mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-10 11:32:58 +01:00
Compare commits
No commits in common. "8287bdeedc29fbc5ae612ce87ae240978aafe48f" and "45e869bbaea44e883b7fb620aee69f9bcc5c6e16" have entirely different histories.
8287bdeedc
...
45e869bbae
6
nextcloud_backup/backend/.eslintrc.cjs
Normal file
6
nextcloud_backup/backend/.eslintrc.cjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
root: true,
|
||||||
|
};
|
@ -1,20 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import tseslint from 'typescript-eslint';
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
eslint.configs.recommended,
|
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
|
||||||
{
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ignores: ["dist/", "eslint.config.js"]
|
|
||||||
}
|
|
||||||
);
|
|
@ -7,11 +7,12 @@
|
|||||||
"build:watch": "tsc -w",
|
"build:watch": "tsc -w",
|
||||||
"build": "tsc --build --verbose",
|
"build": "tsc --build --verbose",
|
||||||
"dev": "pnpm build && concurrently -n tsc,node \"pnpm build:watch\" \"pnpm serve:watch\"",
|
"dev": "pnpm build && concurrently -n tsc,node \"pnpm build:watch\" \"pnpm serve:watch\"",
|
||||||
"lint": "tsc --noEmit && eslint --quiet --fix",
|
"lint": "tsc --noEmit && eslint \"**/*.{js,ts}\" --quiet --fix",
|
||||||
"serve:watch": "nodemon dist/server.js",
|
"serve:watch": "nodemon dist/server.js",
|
||||||
"serve": "node dist/server.js"
|
"serve": "node dist/server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
||||||
"app-root-path": "3.1.0",
|
"app-root-path": "3.1.0",
|
||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
@ -34,24 +35,23 @@
|
|||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.15.3",
|
"packageManager": "pnpm@8.15.3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.6.0",
|
|
||||||
"@tsconfig/recommended": "^1.0.3",
|
"@tsconfig/recommended": "^1.0.3",
|
||||||
"@types/cookie-parser": "^1.4.6",
|
"@types/cookie-parser": "^1.4.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
|
"@types/cron": "^2.4.0",
|
||||||
"@types/errorhandler": "^1.5.3",
|
"@types/errorhandler": "^1.5.3",
|
||||||
"@types/eslint__js": "^8.42.3",
|
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/figlet": "^1.5.8",
|
"@types/figlet": "^1.5.8",
|
||||||
"@types/http-errors": "^2.0.4",
|
"@types/http-errors": "^2.0.4",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
"@types/node": "^20.11.19",
|
"@types/node": "^20.11.19",
|
||||||
|
"@typescript-eslint/parser": "^7.0.1",
|
||||||
"concurrently": "8.2.2",
|
"concurrently": "8.2.2",
|
||||||
"dotenv": "^16.4.4",
|
"dotenv": "^16.4.4",
|
||||||
"eslint": "^9.6.0",
|
"eslint": "8.56.0",
|
||||||
"nodemon": "^3.0.3",
|
"nodemon": "^3.0.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3"
|
||||||
"typescript-eslint": "8.0.0-alpha.41"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,26 +1,31 @@
|
|||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import cors from "cors";
|
import express, { type NextFunction, type Request, type Response } from "express";
|
||||||
import errorHandler from "errorhandler";
|
|
||||||
import express from "express";
|
|
||||||
import createError from "http-errors";
|
import createError from "http-errors";
|
||||||
import morgan from "morgan";
|
import morgan from "morgan";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import logger from "./config/winston.js";
|
import logger from "./config/winston.js";
|
||||||
import apiV2Router from "./routes/apiV2.js";
|
import apiV2Router from "./routes/apiV2.js";
|
||||||
|
import cors from "cors"
|
||||||
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.use(
|
app.use(cors({
|
||||||
cors({
|
origin: true
|
||||||
origin: true,
|
}))
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
app.set("port", process.env.PORT || 3000);
|
app.set("port", process.env.PORT || 3000);
|
||||||
|
|
||||||
|
// app.use(
|
||||||
|
// logger("dev", {
|
||||||
|
// skip: function (req, res) {
|
||||||
|
// return (res.statusCode = 304);
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
morgan("dev", { stream: { write: (message) => logger.debug(message) } })
|
morgan("dev", { stream: { write: (message) => logger.debug(message) } })
|
||||||
);
|
);
|
||||||
@ -41,9 +46,15 @@ app.use((req, res, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// error handler
|
// error handler
|
||||||
if (process.env.NODE_ENV === "development") {
|
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
|
||||||
// only use in development
|
// set locals, only providing error in development
|
||||||
app.use(errorHandler());
|
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");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { existsSync, mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import logger from "./config/winston.js";
|
import logger from "./config/winston.js";
|
||||||
import * as homeAssistantService from "./services/homeAssistantService.js";
|
import * as homeAssistantService from "./services/homeAssistantService.js";
|
||||||
|
import * as settingsTools from "./tools/settingsTools.js";
|
||||||
import * as statusTools from "./tools/status.js";
|
import * as statusTools from "./tools/status.js";
|
||||||
import kleur from "kleur";
|
import kleur from "kleur";
|
||||||
import {
|
import { checkWebdavLogin, createBackupFolder } from "./services/webdavService.js";
|
||||||
checkWebdavLogin,
|
|
||||||
createBackupFolder,
|
|
||||||
} from "./services/webdavService.js";
|
|
||||||
import {
|
import {
|
||||||
getWebdavConfig,
|
getWebdavConfig,
|
||||||
validateWebdavConfig,
|
validateWebdavConfig,
|
||||||
@ -61,15 +59,16 @@ function postInit() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(reason: Error) => {
|
(reason) => {
|
||||||
logger.error("Webdav config: " + kleur.red().bold("FAIL !"));
|
logger.error("Webdav config: " + kleur.red().bold("FAIL !"));
|
||||||
logger.error(reason);
|
logger.error(reason);
|
||||||
messageManager.error("Invalid webdav config", reason.message);
|
messageManager.error("Invalid webdav config", reason.message);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// settingsTools.check(settingsTools.getSettings(), true);
|
settingsTools.check(settingsTools.getSettings(), true);
|
||||||
// cronTools.init();
|
// cronTools.init();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default postInit;
|
export default postInit;
|
||||||
|
@ -10,7 +10,7 @@ actionRouter.post("/backup", (req, res) => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info("All good !");
|
logger.info("All good !");
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((reason) => {
|
||||||
logger.error("Something wrong !");
|
logger.error("Something wrong !");
|
||||||
});
|
});
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
|
@ -4,56 +4,45 @@ import {
|
|||||||
saveBackupConfig,
|
saveBackupConfig,
|
||||||
validateBackupConfig,
|
validateBackupConfig,
|
||||||
} from "../services/backupConfigService.js";
|
} from "../services/backupConfigService.js";
|
||||||
import {
|
import { getWebdavConfig, saveWebdavConfig, validateWebdavConfig } from "../services/webdavConfigService.js";
|
||||||
getWebdavConfig,
|
|
||||||
saveWebdavConfig,
|
|
||||||
validateWebdavConfig,
|
|
||||||
} from "../services/webdavConfigService.js";
|
|
||||||
import { checkWebdavLogin } from "../services/webdavService.js";
|
import { checkWebdavLogin } from "../services/webdavService.js";
|
||||||
import type { BackupConfig } from "../types/services/backupConfig.js";
|
|
||||||
import type { ValidationError } from "joi";
|
|
||||||
import type { WebdavConfig } from "../types/services/webdavConfig.js";
|
|
||||||
|
|
||||||
const configRouter = express.Router();
|
const configRouter = express.Router();
|
||||||
|
|
||||||
configRouter.get("/backup", (req, res) => {
|
configRouter.get("/backup", (req, res, next) => {
|
||||||
res.json(getBackupConfig());
|
res.json(getBackupConfig());
|
||||||
});
|
});
|
||||||
|
|
||||||
configRouter.put("/backup", (req, res) => {
|
configRouter.put("/backup", (req, res, next) => {
|
||||||
validateBackupConfig(req.body as BackupConfig)
|
validateBackupConfig(req.body)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
saveBackupConfig(req.body as BackupConfig);
|
saveBackupConfig(req.body);
|
||||||
res.status(204);
|
res.status(204);
|
||||||
res.send();
|
res.send();
|
||||||
})
|
})
|
||||||
.catch((error: ValidationError) => {
|
.catch((error) => {
|
||||||
res.status(400);
|
res.status(400);
|
||||||
res.json(error.details);
|
res.json(error.details);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
configRouter.get("/webdav", (req, res) => {
|
configRouter.get("/webdav", (req, res, next) => {
|
||||||
res.json(getWebdavConfig());
|
res.json(getWebdavConfig());
|
||||||
});
|
});
|
||||||
|
|
||||||
configRouter.put("/webdav", (req, res) => {
|
configRouter.put("/webdav", (req, res, next) => {
|
||||||
validateWebdavConfig(req.body as WebdavConfig)
|
validateWebdavConfig(req.body)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return checkWebdavLogin(req.body as WebdavConfig, true);
|
return checkWebdavLogin(req.body, true)
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
saveWebdavConfig(req.body as WebdavConfig);
|
saveWebdavConfig(req.body);
|
||||||
res.status(204);
|
res.status(204);
|
||||||
res.send();
|
res.send();
|
||||||
})
|
})
|
||||||
.catch((error: ValidationError) => {
|
.catch((error) => {
|
||||||
res.status(400);
|
res.status(400);
|
||||||
if (error.details) {
|
res.json(error.details ? error.details : error);
|
||||||
res.json(error.details);
|
|
||||||
} else {
|
|
||||||
res.json(error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,15 +3,11 @@ import * as haOsService from "../services/homeAssistantService.js";
|
|||||||
|
|
||||||
const homeAssistantRouter = express.Router();
|
const homeAssistantRouter = express.Router();
|
||||||
|
|
||||||
homeAssistantRouter.get("/backups/", (req, res) => {
|
homeAssistantRouter.get("/backups/", (req, res, next) => {
|
||||||
haOsService
|
haOsService
|
||||||
.getBackups()
|
.getBackups()
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
res.json(
|
res.json(value.body.data.backups.sort((a, b)=> Date.parse(b.date) - Date.parse(a.date)));
|
||||||
value.body.data.backups.sort(
|
|
||||||
(a, b) => Date.parse(b.date) - Date.parse(a.date)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
res.status(500);
|
res.status(500);
|
||||||
@ -19,7 +15,7 @@ homeAssistantRouter.get("/backups/", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
homeAssistantRouter.get("/backup/:slug", (req, res) => {
|
homeAssistantRouter.get("/backup/:slug", (req, res, next) => {
|
||||||
haOsService
|
haOsService
|
||||||
.getBackupInfo(req.params.slug)
|
.getBackupInfo(req.params.slug)
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
@ -31,7 +27,7 @@ homeAssistantRouter.get("/backup/:slug", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
homeAssistantRouter.get("/addons", (req, res) => {
|
homeAssistantRouter.get("/addons", (req, res, next) => {
|
||||||
haOsService
|
haOsService
|
||||||
.getAddonList()
|
.getAddonList()
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
@ -43,7 +39,7 @@ homeAssistantRouter.get("/addons", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
homeAssistantRouter.get("/folders", (req, res) => {
|
homeAssistantRouter.get("/folders", (req, res, next) => {
|
||||||
res.json(haOsService.getFolderList());
|
res.json(haOsService.getFolderList());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import { WebdavDeleteValidation } from "../types/services/webdavValidation.js";
|
|||||||
|
|
||||||
const webdavRouter = express.Router();
|
const webdavRouter = express.Router();
|
||||||
|
|
||||||
webdavRouter.get("/backup/auto", (req, res) => {
|
webdavRouter.get("/backup/auto", (req, res, next) => {
|
||||||
const config = getWebdavConfig();
|
const config = getWebdavConfig();
|
||||||
const backupConf = getBackupConfig();
|
const backupConf = getBackupConfig();
|
||||||
validateWebdavConfig(config)
|
validateWebdavConfig(config)
|
||||||
@ -20,11 +20,8 @@ webdavRouter.get("/backup/auto", (req, res) => {
|
|||||||
return webdavService.checkWebdavLogin(config);
|
return webdavService.checkWebdavLogin(config);
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const value = await webdavService.getBackups(
|
const value = await webdavService
|
||||||
pathTools.auto,
|
.getBackups(pathTools.auto, config, backupConf.nameTemplate);
|
||||||
config,
|
|
||||||
backupConf.nameTemplate
|
|
||||||
);
|
|
||||||
res.json(value);
|
res.json(value);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
@ -33,7 +30,7 @@ webdavRouter.get("/backup/auto", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
webdavRouter.get("/backup/manual", (req, res) => {
|
webdavRouter.get("/backup/manual", (req, res, next) => {
|
||||||
const config = getWebdavConfig();
|
const config = getWebdavConfig();
|
||||||
const backupConf = getBackupConfig();
|
const backupConf = getBackupConfig();
|
||||||
validateWebdavConfig(config)
|
validateWebdavConfig(config)
|
||||||
@ -41,11 +38,8 @@ webdavRouter.get("/backup/manual", (req, res) => {
|
|||||||
return webdavService.checkWebdavLogin(config);
|
return webdavService.checkWebdavLogin(config);
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const value = await webdavService.getBackups(
|
const value = await webdavService
|
||||||
pathTools.manual,
|
.getBackups(pathTools.manual, config, backupConf.nameTemplate);
|
||||||
config,
|
|
||||||
backupConf.nameTemplate
|
|
||||||
);
|
|
||||||
res.json(value);
|
res.json(value);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
@ -54,30 +48,28 @@ webdavRouter.get("/backup/manual", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
webdavRouter.delete("/", (req, res) => {
|
webdavRouter.delete("/", (req, res, next) => {
|
||||||
const body = req.body as WebdavDelete;
|
const body: WebdavDelete = req.body;
|
||||||
const validator = Joi.object(WebdavDeleteValidation);
|
const validator = Joi.object(WebdavDeleteValidation);
|
||||||
const config = getWebdavConfig();
|
const config = getWebdavConfig();
|
||||||
validateWebdavConfig(config)
|
validateWebdavConfig(config).then(() => {
|
||||||
.then(() => {
|
validator
|
||||||
return validator.validateAsync(body);
|
.validateAsync(body)
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return webdavService.checkWebdavLogin(config);
|
return webdavService.checkWebdavLogin(config);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
webdavService
|
webdavService.deleteBackup(body.path, config)
|
||||||
.deleteBackup(body.path, config)
|
.then(()=>{
|
||||||
.then(() => {
|
|
||||||
res.status(201).send();
|
res.status(201).send();
|
||||||
})
|
}).catch((reason)=>{
|
||||||
.catch((reason) => {
|
|
||||||
res.status(500).json(reason);
|
res.status(500).json(reason);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
res.status(400).json(reason);
|
res.status(400).json(reason);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default webdavRouter;
|
export default webdavRouter;
|
||||||
|
@ -4,7 +4,8 @@ import app from "./app.js";
|
|||||||
import logger from "./config/winston.js";
|
import logger from "./config/winston.js";
|
||||||
import postInit from "./postInit.js";
|
import postInit from "./postInit.js";
|
||||||
import figlet from "figlet";
|
import figlet from "figlet";
|
||||||
import kleur from "kleur";
|
import kleur from 'kleur';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error Handler. Provides full stack
|
* Error Handler. Provides full stack
|
||||||
@ -13,17 +14,14 @@ if (process.env.NODE_ENV === "development") {
|
|||||||
app.use(errorHandler());
|
app.use(errorHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start Express server.
|
* Start Express server.
|
||||||
*/
|
*/
|
||||||
const server = app.listen(app.get("port"), () => {
|
const server = app.listen(app.get("port"), () => {
|
||||||
console.log(kleur.yellow().bold(figlet.textSync("NC Backup")));
|
console.log(kleur.yellow().bold(figlet.textSync("NC Backup")))
|
||||||
logger.info(
|
logger.info(
|
||||||
`App is running at ` +
|
`App is running at ` + kleur.green().bold(`http://localhost:${app.get("port")}`) + " in " + kleur.green().bold(app.get("env")) + " mode"
|
||||||
kleur.green().bold(`http://localhost:${app.get("port")}`) +
|
|
||||||
" in " +
|
|
||||||
kleur.green().bold(app.get("env") as string) +
|
|
||||||
" mode"
|
|
||||||
);
|
);
|
||||||
logger.info(kleur.red().bold("Press CTRL-C to stop"));
|
logger.info(kleur.red().bold("Press CTRL-C to stop"));
|
||||||
postInit();
|
postInit();
|
||||||
|
@ -29,9 +29,7 @@ export function getBackupConfig(): BackupConfig {
|
|||||||
saveBackupConfig(defaultConfig);
|
saveBackupConfig(defaultConfig);
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
} else {
|
} else {
|
||||||
return JSON.parse(
|
return JSON.parse(fs.readFileSync(backupConfigPath).toString());
|
||||||
fs.readFileSync(backupConfigPath).toString()
|
|
||||||
) as BackupConfig;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,26 +5,27 @@ import got, {
|
|||||||
RequestError,
|
RequestError,
|
||||||
type OptionsOfJSONResponseBody,
|
type OptionsOfJSONResponseBody,
|
||||||
type PlainResponse,
|
type PlainResponse,
|
||||||
type Progress,
|
|
||||||
type Response,
|
type Response,
|
||||||
} from "got";
|
} from "got";
|
||||||
import { DateTime } from "luxon";
|
|
||||||
import stream from "stream";
|
import stream from "stream";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import logger from "../config/winston.js";
|
import logger from "../config/winston.js";
|
||||||
import messageManager from "../tools/messageManager.js";
|
import messageManager from "../tools/messageManager.js";
|
||||||
|
import * as settingsTools from "../tools/settingsTools.js";
|
||||||
import * as statusTools from "../tools/status.js";
|
import * as statusTools from "../tools/status.js";
|
||||||
import { BackupType } from "../types/services/backupConfig.js";
|
|
||||||
import type { NewBackupPayload } from "../types/services/ha_os_payload.js";
|
import type { NewBackupPayload } from "../types/services/ha_os_payload.js";
|
||||||
import type {
|
import type {
|
||||||
AddonData,
|
AddonData,
|
||||||
|
AddonModel,
|
||||||
BackupData,
|
BackupData,
|
||||||
BackupDetailModel,
|
BackupDetailModel,
|
||||||
BackupModel,
|
BackupModel,
|
||||||
CoreInfoBody,
|
CoreInfoBody,
|
||||||
SupervisorResponse,
|
SupervisorResponse,
|
||||||
} from "../types/services/ha_os_response.js";
|
} from "../types/services/ha_os_response.js";
|
||||||
import { States } from "../types/status.js";
|
import { States, type Status } from "../types/status.js";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import { BackupType } from "../types/services/backupConfig.js";
|
||||||
|
|
||||||
const pipeline = promisify(stream.pipeline);
|
const pipeline = promisify(stream.pipeline);
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ function getVersion(): Promise<Response<SupervisorResponse<CoreInfoBody>>> {
|
|||||||
(result) => {
|
(result) => {
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
(error: Error) => {
|
(error) => {
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
"Fail to fetch Home Assistant version",
|
"Fail to fetch Home Assistant version",
|
||||||
error?.message
|
error?.message
|
||||||
@ -72,7 +73,7 @@ function getAddonList(): Promise<Response<SupervisorResponse<AddonData>>> {
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
(error: RequestError) => {
|
(error) => {
|
||||||
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);
|
||||||
@ -97,7 +98,7 @@ function getBackups(): Promise<Response<SupervisorResponse<BackupData>>> {
|
|||||||
statusTools.setStatus(status);
|
statusTools.setStatus(status);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
(error: RequestError) => {
|
(error) => {
|
||||||
const status = statusTools.getStatus();
|
const status = statusTools.getStatus();
|
||||||
status.hass.ok = false;
|
status.hass.ok = false;
|
||||||
status.hass.last_check = DateTime.now();
|
status.hass.last_check = DateTime.now();
|
||||||
@ -126,7 +127,7 @@ function downloadSnapshot(id: string): Promise<string> {
|
|||||||
return pipeline(
|
return pipeline(
|
||||||
got.stream
|
got.stream
|
||||||
.get(`http://hassio/backups/${id}/download`, option)
|
.get(`http://hassio/backups/${id}/download`, option)
|
||||||
.on("downloadProgress", (e: Progress) => {
|
.on("downloadProgress", (e) => {
|
||||||
const percent = Math.round(e.percent * 100) / 100;
|
const percent = Math.round(e.percent * 100) / 100;
|
||||||
if (status.progress !== percent) {
|
if (status.progress !== percent) {
|
||||||
status.progress = percent;
|
status.progress = percent;
|
||||||
@ -146,7 +147,7 @@ function downloadSnapshot(id: string): Promise<string> {
|
|||||||
);
|
);
|
||||||
return tmp_file;
|
return tmp_file;
|
||||||
},
|
},
|
||||||
(reason: RequestError) => {
|
(reason) => {
|
||||||
fs.unlinkSync(tmp_file);
|
fs.unlinkSync(tmp_file);
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
"Fail to download Home Assistant backup",
|
"Fail to download Home Assistant backup",
|
||||||
@ -169,7 +170,7 @@ function delSnap(id: string) {
|
|||||||
(result) => {
|
(result) => {
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
(reason: RequestError) => {
|
(reason) => {
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
"Fail to delete Homme assistant backup detail.",
|
"Fail to delete Homme assistant backup detail.",
|
||||||
reason.message
|
reason.message
|
||||||
@ -195,7 +196,7 @@ function getBackupInfo(id: string) {
|
|||||||
logger.debug(`Backup size: ${result.body.data.size}`);
|
logger.debug(`Backup size: ${result.body.data.size}`);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
(reason: RequestError) => {
|
(reason) => {
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
"Fail to retrive Homme assistant backup detail.",
|
"Fail to retrive Homme assistant backup detail.",
|
||||||
reason.message
|
reason.message
|
||||||
@ -249,7 +250,7 @@ function createNewBackup(
|
|||||||
statusTools.setStatus(status);
|
statusTools.setStatus(status);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
(reason: RequestError) => {
|
(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);
|
||||||
@ -262,15 +263,19 @@ function createNewBackup(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clean(backups: BackupModel[], numberToKeep: number) {
|
function clean(backups: BackupModel[]) {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
if (backups.length < numberToKeep) {
|
let limit = settingsTools.getSettings().auto_clean_local_keep;
|
||||||
|
if (limit == null) {
|
||||||
|
limit = 5;
|
||||||
|
}
|
||||||
|
if (backups.length < limit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
backups.sort((a, b) => {
|
backups.sort((a, b) => {
|
||||||
return Date.parse(b.date) - Date.parse(a.date);
|
return Date.parse(b.date) - Date.parse(a.date);
|
||||||
});
|
});
|
||||||
const toDel = backups.slice(numberToKeep);
|
const toDel = backups.slice(limit);
|
||||||
for (const i of toDel) {
|
for (const i of toDel) {
|
||||||
promises.push(delSnap(i.slug));
|
promises.push(delSnap(i.slug));
|
||||||
}
|
}
|
||||||
@ -279,7 +284,6 @@ function clean(backups: BackupModel[], numberToKeep: number) {
|
|||||||
let errors = false;
|
let errors = false;
|
||||||
for (const val of values) {
|
for (const val of values) {
|
||||||
if (val.status == "rejected") {
|
if (val.status == "rejected") {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
messageManager.error("Fail to delete backup", val.reason);
|
messageManager.error("Fail to delete backup", val.reason);
|
||||||
logger.error("Fail to delete backup");
|
logger.error("Fail to delete backup");
|
||||||
logger.error(val.reason);
|
logger.error(val.reason);
|
||||||
@ -289,7 +293,7 @@ function clean(backups: BackupModel[], numberToKeep: number) {
|
|||||||
if (errors) {
|
if (errors) {
|
||||||
messageManager.error("Fail to clean backups in Home Assistant");
|
messageManager.error("Fail to clean backups in Home Assistant");
|
||||||
logger.error("Fail to clean backups in Home Assistant");
|
logger.error("Fail to clean backups in Home Assistant");
|
||||||
return Promise.reject(new Error());
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -311,7 +315,7 @@ function uploadSnapshot(path: string) {
|
|||||||
};
|
};
|
||||||
got.stream
|
got.stream
|
||||||
.post(`http://hassio/backups/new/upload`, options)
|
.post(`http://hassio/backups/new/upload`, options)
|
||||||
.on("uploadProgress", (e: Progress) => {
|
.on("uploadProgress", (e) => {
|
||||||
const percent = e.percent;
|
const percent = e.percent;
|
||||||
if (status.progress !== percent) {
|
if (status.progress !== percent) {
|
||||||
status.progress = percent;
|
status.progress = percent;
|
||||||
@ -325,13 +329,13 @@ function uploadSnapshot(path: string) {
|
|||||||
if (res.statusCode !== 200) {
|
if (res.statusCode !== 200) {
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
"Fail to upload backup to Home Assistant",
|
"Fail to upload backup to Home Assistant",
|
||||||
`Code: ${res.statusCode} Body: ${res.body as string}`
|
`Code: ${res.statusCode} Body: ${res.body}`
|
||||||
);
|
);
|
||||||
logger.error("Fail to upload backup to Home Assistant");
|
logger.error("Fail to upload backup to Home Assistant");
|
||||||
logger.error(`Code: ${res.statusCode}`);
|
logger.error(`Code: ${res.statusCode}`);
|
||||||
logger.error(`Body: ${res.body as string}`);
|
logger.error(`Body: ${res.body}`);
|
||||||
fs.unlinkSync(path);
|
fs.unlinkSync(path);
|
||||||
reject(new Error(res.statusCode.toString()));
|
reject(res.statusCode);
|
||||||
} else {
|
} else {
|
||||||
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
|
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
|
||||||
const status = statusTools.getStatus();
|
const status = statusTools.getStatus();
|
||||||
@ -384,17 +388,17 @@ function stopAddons(addonSlugs: string[]) {
|
|||||||
statusTools.setStatus(status);
|
statusTools.setStatus(status);
|
||||||
for (const val of values) {
|
for (const val of values) {
|
||||||
if (val.status == "rejected") {
|
if (val.status == "rejected") {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
messageManager.error("Fail to stop addon", val.reason);
|
messageManager.error("Fail to stop addon", val.reason);
|
||||||
logger.error("Fail to stop addon");
|
logger.error("Fail to stop addon");
|
||||||
logger.error(val.reason);
|
logger.error(val.reason);
|
||||||
|
logger.error;
|
||||||
errors = true;
|
errors = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (errors) {
|
if (errors) {
|
||||||
messageManager.error("Fail to stop addon");
|
messageManager.error("Fail to stop addon");
|
||||||
logger.error("Fail to stop addon");
|
logger.error("Fail to stop addon");
|
||||||
return Promise.reject(new Error());
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -424,7 +428,6 @@ function startAddons(addonSlugs: string[]) {
|
|||||||
let errors = false;
|
let errors = false;
|
||||||
for (const val of values) {
|
for (const val of values) {
|
||||||
if (val.status == "rejected") {
|
if (val.status == "rejected") {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
messageManager.error("Fail to start addon", val.reason);
|
messageManager.error("Fail to start addon", val.reason);
|
||||||
logger.error("Fail to start addon");
|
logger.error("Fail to start addon");
|
||||||
logger.error(val.reason);
|
logger.error(val.reason);
|
||||||
@ -434,7 +437,7 @@ function startAddons(addonSlugs: string[]) {
|
|||||||
if (errors) {
|
if (errors) {
|
||||||
messageManager.error("Fail to start addon");
|
messageManager.error("Fail to start addon");
|
||||||
logger.error("Fail to start addon");
|
logger.error("Fail to start addon");
|
||||||
return Promise.reject(new Error());
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -464,7 +467,7 @@ export function getFolderList() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function publish_state() {
|
function publish_state(state: Status) {
|
||||||
// let data_error_sensor = {
|
// let data_error_sensor = {
|
||||||
// state: state.status == "error" ? "on" : "off",
|
// state: state.status == "error" ? "on" : "off",
|
||||||
// attributes: {
|
// attributes: {
|
||||||
@ -529,15 +532,15 @@ function publish_state() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
clean,
|
|
||||||
createNewBackup,
|
|
||||||
downloadSnapshot,
|
|
||||||
getAddonList,
|
|
||||||
getBackupInfo,
|
|
||||||
getBackups,
|
|
||||||
getVersion,
|
getVersion,
|
||||||
publish_state,
|
getAddonList,
|
||||||
startAddons,
|
getBackups,
|
||||||
stopAddons,
|
downloadSnapshot,
|
||||||
|
createNewBackup,
|
||||||
uploadSnapshot,
|
uploadSnapshot,
|
||||||
|
stopAddons,
|
||||||
|
startAddons,
|
||||||
|
clean,
|
||||||
|
publish_state,
|
||||||
|
getBackupInfo,
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { unlinkSync } from "fs";
|
|
||||||
import { DateTime } from "luxon";
|
|
||||||
import logger from "../config/winston.js";
|
|
||||||
import messageManager from "../tools/messageManager.js";
|
|
||||||
import * as statusTools from "../tools/status.js";
|
|
||||||
import { BackupType } from "../types/services/backupConfig.js";
|
|
||||||
import type { AddonModel } from "../types/services/ha_os_response.js";
|
import type { AddonModel } from "../types/services/ha_os_response.js";
|
||||||
import { WorkflowType } from "../types/services/orchecstrator.js";
|
import { WorkflowType } from "../types/services/orchecstrator.js";
|
||||||
import * as backupConfigService from "./backupConfigService.js";
|
import * as backupConfigService from "./backupConfigService.js";
|
||||||
import * as homeAssistantService from "./homeAssistantService.js";
|
import * as homeAssistantService from "./homeAssistantService.js";
|
||||||
import { getBackupFolder, getWebdavConfig } from "./webdavConfigService.js";
|
import { getBackupFolder, getWebdavConfig } from "./webdavConfigService.js";
|
||||||
import * as webDavService from "./webdavService.js";
|
import * as webDavService from "./webdavService.js";
|
||||||
|
import * as statusTools from "../tools/status.js";
|
||||||
|
import { stat, unlinkSync } from "fs";
|
||||||
|
import logger from "../config/winston.js";
|
||||||
|
import { BackupType } from "../types/services/backupConfig.js";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import messageManager from "../tools/messageManager.js";
|
||||||
|
|
||||||
export function doBackupWorkflow(type: WorkflowType) {
|
export function doBackupWorkflow(type: WorkflowType) {
|
||||||
let name = "";
|
let name = "";
|
||||||
@ -38,7 +38,7 @@ export function doBackupWorkflow(type: WorkflowType) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return homeAssistantService.stopAddons(addonsToStartStop);
|
return homeAssistantService.stopAddons(addonsToStartStop);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then((response) => {
|
||||||
if (backupConfig.backupType == BackupType.FULL) {
|
if (backupConfig.backupType == BackupType.FULL) {
|
||||||
return homeAssistantService.createNewBackup(
|
return homeAssistantService.createNewBackup(
|
||||||
name,
|
name,
|
||||||
@ -66,23 +66,21 @@ export function doBackupWorkflow(type: WorkflowType) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
response.body.data.slug;
|
||||||
return homeAssistantService.downloadSnapshot(response.body.data.slug);
|
return homeAssistantService.downloadSnapshot(response.body.data.slug);
|
||||||
})
|
})
|
||||||
.then((tmpFile) => {
|
.then((tmpFile) => {
|
||||||
tmpBackupFile = tmpFile;
|
tmpBackupFile = tmpFile;
|
||||||
if (webdavConfig.chunckedUpload) {
|
|
||||||
return webDavService.chunkedUpload(
|
return webDavService.chunkedUpload(
|
||||||
tmpFile,
|
tmpFile,
|
||||||
getBackupFolder(type, webdavConfig) + name,
|
getBackupFolder(type, webdavConfig) + name,
|
||||||
webdavConfig
|
webdavConfig
|
||||||
);
|
);
|
||||||
} else {
|
// return webDavService.webdavUploadFile(
|
||||||
return webDavService.webdavUploadFile(
|
// tmpFile,
|
||||||
tmpFile,
|
// getBackupFolder(type, webdavConfig) + name,
|
||||||
getBackupFolder(type, webdavConfig) + name,
|
// webdavConfig
|
||||||
webdavConfig
|
// );
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info("Backup workflow finished successfully !");
|
logger.info("Backup workflow finished successfully !");
|
||||||
@ -98,7 +96,7 @@ export function doBackupWorkflow(type: WorkflowType) {
|
|||||||
if (tmpBackupFile != "") {
|
if (tmpBackupFile != "") {
|
||||||
unlinkSync(tmpBackupFile);
|
unlinkSync(tmpBackupFile);
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error());
|
return Promise.reject();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
import logger from "../config/winston.js";
|
import logger from "../config/winston.js";
|
||||||
import * as pathTools from "../tools/pathTools.js";
|
|
||||||
import { default_root } from "../tools/pathTools.js";
|
import { default_root } from "../tools/pathTools.js";
|
||||||
import { WorkflowType } from "../types/services/orchecstrator.js";
|
|
||||||
import {
|
import {
|
||||||
type WebdavConfig,
|
type WebdavConfig,
|
||||||
WebdavEndpointType,
|
WebdavEndpointType,
|
||||||
} from "../types/services/webdavConfig.js";
|
} from "../types/services/webdavConfig.js";
|
||||||
import WebdavConfigValidation from "../types/services/webdavConfigValidation.js";
|
import WebdavConfigValidation from "../types/services/webdavConfigValidation.js";
|
||||||
|
import { BackupType } from "../types/services/backupConfig.js";
|
||||||
|
import * as pathTools from "../tools/pathTools.js";
|
||||||
|
import { WorkflowType } from "../types/services/orchecstrator.js";
|
||||||
|
import e from "express";
|
||||||
|
|
||||||
const webdavConfigPath = "/data/webdavConfigV2.json";
|
const webdavConfigPath = "/data/webdavConfigV2.json";
|
||||||
const NEXTCLOUD_ENDPOINT = "/remote.php/dav/files/$username";
|
const NEXTCLOUD_ENDPOINT = "/remote.php/dav/files/$username";
|
||||||
@ -32,9 +34,7 @@ export function getWebdavConfig(): WebdavConfig {
|
|||||||
saveWebdavConfig(defaultConfig);
|
saveWebdavConfig(defaultConfig);
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
} else {
|
} else {
|
||||||
return JSON.parse(
|
return JSON.parse(fs.readFileSync(webdavConfigPath).toString());
|
||||||
fs.readFileSync(webdavConfigPath).toString()
|
|
||||||
) as WebdavConfig;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
import { randomUUID } from "crypto";
|
|
||||||
import { XMLParser } from "fast-xml-parser";
|
import { XMLParser } from "fast-xml-parser";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import got, {
|
import got, {
|
||||||
@ -18,9 +13,10 @@ import * as pathTools from "../tools/pathTools.js";
|
|||||||
import * as statusTools from "../tools/status.js";
|
import * as statusTools from "../tools/status.js";
|
||||||
import type { WebdavBackup } from "../types/services/webdav.js";
|
import type { WebdavBackup } from "../types/services/webdav.js";
|
||||||
import type { WebdavConfig } from "../types/services/webdavConfig.js";
|
import type { WebdavConfig } from "../types/services/webdavConfig.js";
|
||||||
import { States } from "../types/status.js";
|
|
||||||
import { templateToRegexp } from "./backupConfigService.js";
|
import { templateToRegexp } from "./backupConfigService.js";
|
||||||
import { getChunkEndpoint, getEndpoint } from "./webdavConfigService.js";
|
import { getChunkEndpoint, getEndpoint } from "./webdavConfigService.js";
|
||||||
|
import { States } from "../types/status.js";
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
|
||||||
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MiB Same as desktop client
|
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MiB Same as desktop client
|
||||||
const CHUNK_NUMBER_SIZE = 5; // To add landing "0"
|
const CHUNK_NUMBER_SIZE = 5; // To add landing "0"
|
||||||
@ -56,9 +52,9 @@ export function checkWebdavLogin(
|
|||||||
status.webdav.last_check = DateTime.now();
|
status.webdav.last_check = DateTime.now();
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
(reason: RequestError) => {
|
(reason) => {
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
messageManager.error("Fail to connect to Webdav", reason.message);
|
messageManager.error("Fail to connect to Webdav", reason?.message);
|
||||||
}
|
}
|
||||||
const status = statusTools.getStatus();
|
const status = statusTools.getStatus();
|
||||||
status.webdav = {
|
status.webdav = {
|
||||||
@ -94,7 +90,7 @@ export async function createBackupFolder(conf: WebdavConfig) {
|
|||||||
status.webdav.folder_created = false;
|
status.webdav.folder_created = false;
|
||||||
status.webdav.last_check = DateTime.now();
|
status.webdav.last_check = DateTime.now();
|
||||||
statusTools.setStatus(status);
|
statusTools.setStatus(status);
|
||||||
return Promise.reject(error as Error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,7 +110,7 @@ export async function createBackupFolder(conf: WebdavConfig) {
|
|||||||
status.webdav.folder_created = false;
|
status.webdav.folder_created = false;
|
||||||
status.webdav.last_check = DateTime.now();
|
status.webdav.last_check = DateTime.now();
|
||||||
statusTools.setStatus(status);
|
statusTools.setStatus(status);
|
||||||
return Promise.reject(error as Error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,7 +139,7 @@ export function getBackups(
|
|||||||
) {
|
) {
|
||||||
const status = statusTools.getStatus();
|
const status = statusTools.getStatus();
|
||||||
if (!status.webdav.logged_in && !status.webdav.folder_created) {
|
if (!status.webdav.logged_in && !status.webdav.folder_created) {
|
||||||
return Promise.reject(new Error("Not logged in"));
|
return Promise.reject("Not logged in");
|
||||||
}
|
}
|
||||||
const endpoint = getEndpoint(config);
|
const endpoint = getEndpoint(config);
|
||||||
return got(config.url + endpoint + config.backupDir + folder, {
|
return got(config.url + endpoint + config.backupDir + folder, {
|
||||||
@ -163,7 +159,7 @@ export function getBackups(
|
|||||||
);
|
);
|
||||||
return extractBackupInfo(data, nameTemplate);
|
return extractBackupInfo(data, nameTemplate);
|
||||||
},
|
},
|
||||||
(reason: RequestError) => {
|
(reason) => {
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
`Fail to retrive webdav backups in ${folder} folder`
|
`Fail to retrive webdav backups in ${folder} folder`
|
||||||
);
|
);
|
||||||
@ -215,8 +211,11 @@ export function deleteBackup(path: string, config: WebdavConfig) {
|
|||||||
(response) => {
|
(response) => {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
(reason: RequestError) => {
|
(reason) => {
|
||||||
messageManager.error("Fail to delete backup in webdav", reason.message);
|
messageManager.error(
|
||||||
|
"Fail to delete backup in webdav",
|
||||||
|
reason?.message
|
||||||
|
);
|
||||||
logger.error(`Fail to delete backup in Cloud`);
|
logger.error(`Fail to delete backup in Cloud`);
|
||||||
logger.error(reason);
|
logger.error(reason);
|
||||||
return Promise.reject(reason);
|
return Promise.reject(reason);
|
||||||
@ -303,13 +302,13 @@ export function webdavUploadFile(
|
|||||||
if (res.statusCode != 201 && res.statusCode != 204) {
|
if (res.statusCode != 201 && res.statusCode != 204) {
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
"Fail to upload file to Cloud.",
|
"Fail to upload file to Cloud.",
|
||||||
`Code: ${res.statusCode} Body: ${res.body as string}`
|
`Code: ${res.statusCode} Body: ${res.body}`
|
||||||
);
|
);
|
||||||
logger.error(`Fail to upload file to Cloud`);
|
logger.error(`Fail to upload file to Cloud`);
|
||||||
logger.error(`Code: ${res.statusCode}`);
|
logger.error(`Code: ${res.statusCode}`);
|
||||||
logger.error(`Body: ${res.body as string}`);
|
logger.error(`Body: ${res.body}`);
|
||||||
fs.unlinkSync(localPath);
|
fs.unlinkSync(localPath);
|
||||||
reject(new Error(res.statusCode.toString()));
|
reject(res);
|
||||||
} else {
|
} else {
|
||||||
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
|
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
|
||||||
fs.unlinkSync(localPath);
|
fs.unlinkSync(localPath);
|
||||||
@ -341,10 +340,6 @@ export async function chunkedUpload(
|
|||||||
const chunkEndpoint = getChunkEndpoint(config);
|
const chunkEndpoint = getChunkEndpoint(config);
|
||||||
const chunkedUrl = config.url + chunkEndpoint + uuid;
|
const chunkedUrl = config.url + chunkEndpoint + uuid;
|
||||||
const finalDestination = config.url + getEndpoint(config) + webdavPath;
|
const finalDestination = config.url + getEndpoint(config) + webdavPath;
|
||||||
const status = statusTools.getStatus();
|
|
||||||
status.status = States.BKUP_UPLOAD_CLOUD;
|
|
||||||
status.progress = 0;
|
|
||||||
statusTools.setStatus(status);
|
|
||||||
try {
|
try {
|
||||||
await initChunkedUpload(chunkedUrl, finalDestination, config);
|
await initChunkedUpload(chunkedUrl, finalDestination, config);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -371,7 +366,7 @@ export async function chunkedUpload(
|
|||||||
let start = 0;
|
let start = 0;
|
||||||
let end = fileSize > CHUNK_SIZE ? CHUNK_SIZE : fileSize;
|
let end = fileSize > CHUNK_SIZE ? CHUNK_SIZE : fileSize;
|
||||||
let current_size = end;
|
let current_size = end;
|
||||||
// const uploadedBytes = 0;
|
let uploadedBytes = 0;
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (start < fileSize) {
|
while (start < fileSize) {
|
||||||
@ -401,12 +396,12 @@ export async function chunkedUpload(
|
|||||||
messageManager.error(
|
messageManager.error(
|
||||||
"Fail to upload file to Cloud.",
|
"Fail to upload file to Cloud.",
|
||||||
`Code: ${(error as PlainResponse).statusCode} Body: ${
|
`Code: ${(error as PlainResponse).statusCode} Body: ${
|
||||||
(error as PlainResponse).body as string
|
(error as PlainResponse).body
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
logger.error(`Fail to upload file to Cloud`);
|
logger.error(`Fail to upload file to Cloud`);
|
||||||
logger.error(`Code: ${(error as PlainResponse).statusCode}`);
|
logger.error(`Code: ${(error as PlainResponse).statusCode}`);
|
||||||
logger.error(`Body: ${(error as PlainResponse).body as string}`);
|
logger.error(`Body: ${(error as PlainResponse).body}`);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -467,7 +462,7 @@ export function uploadChunk(
|
|||||||
resolve(res);
|
resolve(res);
|
||||||
} else {
|
} else {
|
||||||
logger.error(`Fail to upload chunk: ${res.statusCode}`);
|
logger.error(`Fail to upload chunk: ${res.statusCode}`);
|
||||||
reject(new Error(res.statusCode.toString()));
|
reject(res);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on("error", (err) => {
|
.on("error", (err) => {
|
||||||
|
213
nextcloud_backup/backend/src/tools/settingsTools.ts
Normal file
213
nextcloud_backup/backend/src/tools/settingsTools.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { CronJob } from "cron";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import logger from "../config/winston.js";
|
||||||
|
import type { Settings } from "../types/settings.js";
|
||||||
|
|
||||||
|
const settingsPath = "/data/backup_conf.json";
|
||||||
|
|
||||||
|
function check_cron(conf: Settings) {
|
||||||
|
if (conf.cron_base != null) {
|
||||||
|
if (
|
||||||
|
conf.cron_base == "1" ||
|
||||||
|
conf.cron_base == "2" ||
|
||||||
|
conf.cron_base == "3"
|
||||||
|
) {
|
||||||
|
if (conf.cron_hour != null && conf.cron_hour.match(/\d\d:\d\d/)) {
|
||||||
|
if (conf.cron_base === "1") return true;
|
||||||
|
} else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf.cron_base === "2") {
|
||||||
|
return (
|
||||||
|
conf.cron_weekday != null &&
|
||||||
|
conf.cron_weekday >= 0 &&
|
||||||
|
conf.cron_weekday <= 6
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf.cron_base === "3") {
|
||||||
|
return (
|
||||||
|
conf.cron_month_day != null &&
|
||||||
|
conf.cron_month_day >= 1 &&
|
||||||
|
conf.cron_month_day <= 28
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf.cron_base === "4") {
|
||||||
|
if (conf.cron_custom != null) {
|
||||||
|
try {
|
||||||
|
// TODO Need to be destroy
|
||||||
|
new CronJob(conf.cron_custom, () => {
|
||||||
|
//Do nothing
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf.cron_base === "0") return true;
|
||||||
|
} else return false;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(conf: Settings, fallback = false) {
|
||||||
|
let needSave = false;
|
||||||
|
if (!check_cron(conf)) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for cron settings, fallback to default ");
|
||||||
|
conf.cron_base = "0";
|
||||||
|
conf.cron_hour = "00:00";
|
||||||
|
conf.cron_weekday = 0;
|
||||||
|
conf.cron_month_day = 1;
|
||||||
|
conf.cron_custom = "5 4 * * *";
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for cron settings");
|
||||||
|
return [false, "Bad cron settings"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!conf.name_template) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'name_template', fallback to default ");
|
||||||
|
conf.name_template = "{type}-{ha_version}-{date}_{hour}";
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'name_template'");
|
||||||
|
return [false, "Bad value for 'name_template'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
conf.auto_clean_local_keep == undefined ||
|
||||||
|
!/^\d+$/.test(conf.auto_clean_local_keep)
|
||||||
|
) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'auto_clean_local_keep', fallback to 5 ");
|
||||||
|
conf.auto_clean_local_keep = "5";
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'auto_clean_local_keep'");
|
||||||
|
return [false, "Bad value for 'auto_clean_local_keep'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
conf.auto_clean_backup_keep == undefined ||
|
||||||
|
!/^\d+$/.test(conf.auto_clean_backup_keep)
|
||||||
|
) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'auto_clean_backup_keep', fallback to 5 ");
|
||||||
|
conf.auto_clean_backup_keep = "5";
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'auto_clean_backup_keep'");
|
||||||
|
return [false, "Bad value for 'auto_clean_backup_keep'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conf.auto_clean_local == undefined) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'auto_clean_local', fallback to false ");
|
||||||
|
conf.auto_clean_local = "false";
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'auto_clean_local'");
|
||||||
|
return [false, "Bad value for 'auto_clean_local'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conf.auto_clean_backup == undefined) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'auto_clean_backup', fallback to false ");
|
||||||
|
conf.auto_clean_backup = "false";
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'auto_clean_backup'");
|
||||||
|
return [false, "Bad value for 'auto_clean_backup'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conf.exclude_addon == undefined) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'exclude_addon', fallback to [] ");
|
||||||
|
conf.exclude_addon = [];
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'exclude_addon'");
|
||||||
|
return [false, "Bad value for 'exclude_addon'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conf.exclude_folder == undefined) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'exclude_folder', fallback to [] ");
|
||||||
|
conf.exclude_folder = [];
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'exclude_folder'");
|
||||||
|
return [false, "Bad value for 'exclude_folder'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conf.auto_stop_addon == undefined) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'auto_stop_addon', fallback to [] ");
|
||||||
|
conf.auto_stop_addon = [];
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'auto_stop_addon'");
|
||||||
|
return [false, "Bad value for 'auto_stop_addon'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(conf.exclude_folder)) {
|
||||||
|
logger.debug("exclude_folder is not array (Empty value), reset...");
|
||||||
|
conf.exclude_folder = [];
|
||||||
|
needSave = true;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(conf.exclude_addon)) {
|
||||||
|
logger.debug("exclude_addon is not array (Empty value), reset...");
|
||||||
|
conf.exclude_addon = [];
|
||||||
|
needSave = true;
|
||||||
|
}
|
||||||
|
if (conf.password_protected == undefined) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'password_protected', fallback to false ");
|
||||||
|
conf.password_protected = "false";
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'password_protect_value'");
|
||||||
|
return [false, "Bad value for 'password_protect_value'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf.password_protect_value == null) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.warn("Bad value for 'password_protect_value', fallback to '' ");
|
||||||
|
conf.password_protect_value = "";
|
||||||
|
} else {
|
||||||
|
logger.error("Bad value for 'password_protect_value'");
|
||||||
|
return [false, "Bad value for 'password_protect_value'"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallback || needSave) {
|
||||||
|
setSettings(conf);
|
||||||
|
}
|
||||||
|
return [true, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatedName(is_manual: boolean, ha_version: string) {
|
||||||
|
const setting = getSettings();
|
||||||
|
let template = setting.name_template;
|
||||||
|
template = template.replace("{type_low}", is_manual ? "manual" : "auto");
|
||||||
|
template = template.replace("{type}", is_manual ? "Manual" : "Auto");
|
||||||
|
template = template.replace("{ha_version}", ha_version);
|
||||||
|
const now = DateTime.now().setLocale("en");
|
||||||
|
template = template.replace("{hour_12}", now.toFormat("hhmma"));
|
||||||
|
template = template.replace("{hour}", now.toFormat("HHmm"));
|
||||||
|
template = template.replace("{date}", now.toFormat("yyyy-MM-dd"));
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSettings() {
|
||||||
|
if (!fs.existsSync(settingsPath)) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return JSON.parse(fs.readFileSync(settingsPath).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSettings(settings: Settings) {
|
||||||
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getSettings, setSettings, check, check_cron, getFormatedName };
|
@ -1,3 +1,4 @@
|
|||||||
|
import { publish_state } from "../services/homeAssistantService.js";
|
||||||
import { States, type Status } from "../types/status.js";
|
import { States, type Status } from "../types/status.js";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
@ -32,6 +33,6 @@ 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;
|
||||||
// publish_state(status);
|
publish_state(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
import { WebdavEndpointType } from "./webdavConfig.js";
|
import { WebdavEndpointType } from "./webdavConfig.js";
|
||||||
|
|
||||||
|
|
||||||
const WebdavConfigValidation = {
|
const WebdavConfigValidation = {
|
||||||
url: Joi.string().not().empty().uri().required().label("Url"),
|
url: Joi.string().not().empty().uri().required().label("Url"),
|
||||||
username: Joi.string().not().empty().label("Username"),
|
username: Joi.string().not().empty().label("Username"),
|
||||||
@ -9,22 +10,18 @@ const WebdavConfigValidation = {
|
|||||||
allowSelfSignedCerts: Joi.boolean().label("Allow self signed certificate"),
|
allowSelfSignedCerts: Joi.boolean().label("Allow self signed certificate"),
|
||||||
chunckedUpload: Joi.boolean().required().label("Chuncked upload"),
|
chunckedUpload: Joi.boolean().required().label("Chuncked upload"),
|
||||||
webdavEndpoint: Joi.object({
|
webdavEndpoint: Joi.object({
|
||||||
type: Joi.string()
|
type: Joi.string().valid(WebdavEndpointType.CUSTOM, WebdavEndpointType.NEXTCLOUD).required(),
|
||||||
.valid(WebdavEndpointType.CUSTOM, WebdavEndpointType.NEXTCLOUD)
|
|
||||||
.required(),
|
|
||||||
customEndpoint: Joi.alternatives().conditional("type", {
|
customEndpoint: Joi.alternatives().conditional("type", {
|
||||||
is: WebdavEndpointType.CUSTOM,
|
is: WebdavEndpointType.CUSTOM,
|
||||||
then: Joi.string().not().empty().required(),
|
then: Joi.string().not().empty().required,
|
||||||
otherwise: Joi.disallow(),
|
otherwise: Joi.disallow()
|
||||||
}),
|
}),
|
||||||
customChunkEndpoint: Joi.alternatives().conditional("type", {
|
customChunkEndpoint: Joi.alternatives().conditional("type", {
|
||||||
is: WebdavEndpointType.CUSTOM,
|
is: WebdavEndpointType.CUSTOM,
|
||||||
then: Joi.string().not().empty().required(),
|
then: Joi.string().not().empty().required,
|
||||||
otherwise: Joi.disallow(),
|
otherwise: Joi.disallow()
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
.required()
|
}).required().label("Webdav endpoint"),
|
||||||
.label("Webdav endpoint"),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export default WebdavConfigValidation;
|
export default WebdavConfigValidation;
|
@ -3,12 +3,12 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"module": "nodenext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "NodeNext",
|
||||||
"target": "ES2022",
|
"target": "es6",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"strict": true
|
"strict": true,
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"]
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user