mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-04 16:42:58 +01:00
Add cron
This commit is contained in:
parent
96233dcd7b
commit
80b390283c
@ -15,7 +15,7 @@
|
|||||||
"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",
|
||||||
"cron": "3.1.6",
|
"cron": "3.1.7",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"errorhandler": "^1.5.1",
|
"errorhandler": "^1.5.1",
|
||||||
"express": "4.18.2",
|
"express": "4.18.2",
|
||||||
|
@ -15,8 +15,8 @@ dependencies:
|
|||||||
specifier: ^2.8.5
|
specifier: ^2.8.5
|
||||||
version: 2.8.5
|
version: 2.8.5
|
||||||
cron:
|
cron:
|
||||||
specifier: 3.1.6
|
specifier: 3.1.7
|
||||||
version: 3.1.6
|
version: 3.1.7
|
||||||
debug:
|
debug:
|
||||||
specifier: 4.3.4
|
specifier: 4.3.4
|
||||||
version: 4.3.4(supports-color@5.5.0)
|
version: 4.3.4(supports-color@5.5.0)
|
||||||
@ -400,13 +400,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/luxon@3.3.8:
|
|
||||||
resolution: {integrity: sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/luxon@3.4.2:
|
/@types/luxon@3.4.2:
|
||||||
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
|
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/mime@1.3.5:
|
/@types/mime@1.3.5:
|
||||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||||
@ -928,10 +923,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/cron@3.1.6:
|
/cron@3.1.7:
|
||||||
resolution: {integrity: sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==}
|
resolution: {integrity: sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/luxon': 3.3.8
|
'@types/luxon': 3.4.2
|
||||||
luxon: 3.4.4
|
luxon: 3.4.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
validateWebdavConfig,
|
validateWebdavConfig,
|
||||||
} from "./services/webdavConfigService.js";
|
} from "./services/webdavConfigService.js";
|
||||||
import messageManager from "./tools/messageManager.js";
|
import messageManager from "./tools/messageManager.js";
|
||||||
|
import { initCron } from "./services/cronService.js";
|
||||||
|
import { getBackupConfig } from "./services/backupConfigService.js";
|
||||||
|
|
||||||
function postInit() {
|
function postInit() {
|
||||||
logger.info(`Log level: ${process.env.LOG_LEVEL}`);
|
logger.info(`Log level: ${process.env.LOG_LEVEL}`);
|
||||||
@ -39,34 +41,46 @@ function postInit() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const webdavConf = getWebdavConfig();
|
const webdavConf = getWebdavConfig();
|
||||||
validateWebdavConfig(webdavConf).then(
|
validateWebdavConfig(webdavConf)
|
||||||
() => {
|
.then(
|
||||||
logger.info("Webdav config: " + kleur.green().bold("Go !"));
|
() => {
|
||||||
checkWebdavLogin(webdavConf).then(
|
logger.info("Webdav config: " + kleur.green().bold("Go !"));
|
||||||
() => {
|
return checkWebdavLogin(webdavConf);
|
||||||
logger.info("Webdav : " + kleur.green().bold("Go !"));
|
},
|
||||||
createBackupFolder(webdavConf).then(
|
(reason: Error) => {
|
||||||
() => {
|
logger.error("Webdav config: " + kleur.red().bold("FAIL !"));
|
||||||
logger.info("Webdav fodlers: " + kleur.green().bold("Go !"));
|
logger.error(reason);
|
||||||
},
|
messageManager.error("Invalid webdav config", reason.message);
|
||||||
(reason) => {
|
}
|
||||||
logger.error("Webdav folders: " + kleur.red().bold("FAIL !"));
|
)
|
||||||
logger.error(reason);
|
.then(
|
||||||
}
|
() => {
|
||||||
);
|
logger.info("Webdav : " + kleur.green().bold("Go !"));
|
||||||
},
|
return createBackupFolder(webdavConf);
|
||||||
(reason) => {
|
},
|
||||||
logger.error("Webdav : " + kleur.red().bold("FAIL !"));
|
(reason) => {
|
||||||
logger.error(reason);
|
logger.error("Webdav : " + kleur.red().bold("FAIL !"));
|
||||||
}
|
logger.error(reason);
|
||||||
);
|
}
|
||||||
},
|
)
|
||||||
(reason: Error) => {
|
.then(
|
||||||
logger.error("Webdav config: " + kleur.red().bold("FAIL !"));
|
() => {
|
||||||
logger.error(reason);
|
logger.info("Webdav fodlers: " + kleur.green().bold("Go !"));
|
||||||
messageManager.error("Invalid webdav config", reason.message);
|
return initCron(getBackupConfig());
|
||||||
}
|
},
|
||||||
);
|
(reason) => {
|
||||||
|
logger.error("Webdav folders: " + kleur.red().bold("FAIL !"));
|
||||||
|
logger.error(reason);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
logger.info("Cron: " + kleur.green().bold("Go !"));
|
||||||
|
},
|
||||||
|
(reason) => {
|
||||||
|
logger.info("Cron: " + kleur.red().bold("FAIL !"));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// settingsTools.check(settingsTools.getSettings(), true);
|
// settingsTools.check(settingsTools.getSettings(), true);
|
||||||
// cronTools.init();
|
// cronTools.init();
|
||||||
|
@ -22,13 +22,24 @@ configRouter.get("/backup", (req, res) => {
|
|||||||
|
|
||||||
configRouter.put("/backup", (req, res) => {
|
configRouter.put("/backup", (req, res) => {
|
||||||
validateBackupConfig(req.body as BackupConfig)
|
validateBackupConfig(req.body as BackupConfig)
|
||||||
.then(() => {
|
.then(
|
||||||
saveBackupConfig(req.body as BackupConfig);
|
() => {
|
||||||
res.status(204).send();
|
return saveBackupConfig(req.body as BackupConfig);
|
||||||
})
|
},
|
||||||
.catch((error: ValidationError) => {
|
(error: ValidationError) => {
|
||||||
res.status(400).json(error.details);
|
res.status(400).json(error.details);
|
||||||
});
|
}
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
res.status(204).send();
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
res.status(400).json({
|
||||||
|
message: "Fail to init cron, please check cron config",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
configRouter.get("/webdav", (req, res) => {
|
configRouter.get("/webdav", (req, res) => {
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
import backupConfigValidation from "../types/services/backupConfigValidation.js";
|
import backupConfigValidation from "../types/services/backupConfigValidation.js";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { WorkflowType } from "../types/services/orchecstrator.js";
|
import { WorkflowType } from "../types/services/orchecstrator.js";
|
||||||
|
import { initCron } from "./cronService.js";
|
||||||
|
|
||||||
const backupConfigPath = "/data/backupConfigV2.json";
|
const backupConfigPath = "/data/backupConfigV2.json";
|
||||||
|
|
||||||
@ -20,13 +21,14 @@ export function validateBackupConfig(config: BackupConfig) {
|
|||||||
|
|
||||||
export function saveBackupConfig(config: BackupConfig) {
|
export function saveBackupConfig(config: BackupConfig) {
|
||||||
fs.writeFileSync(backupConfigPath, JSON.stringify(config, undefined, 2));
|
fs.writeFileSync(backupConfigPath, JSON.stringify(config, undefined, 2));
|
||||||
|
return initCron(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBackupConfig(): BackupConfig {
|
export function getBackupConfig(): BackupConfig {
|
||||||
if (!fs.existsSync(backupConfigPath)) {
|
if (!fs.existsSync(backupConfigPath)) {
|
||||||
logger.warn("Config file not found, creating default one !");
|
logger.warn("Config file not found, creating default one !");
|
||||||
const defaultConfig = getBackupDefaultConfig();
|
const defaultConfig = getBackupDefaultConfig();
|
||||||
saveBackupConfig(defaultConfig);
|
saveBackupConfig(defaultConfig).catch(() => {});
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
} else {
|
} else {
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
|
119
nextcloud_backup/backend/src/services/cronService.ts
Normal file
119
nextcloud_backup/backend/src/services/cronService.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import { CronJob } from "cron";
|
||||||
|
import {
|
||||||
|
CronMode,
|
||||||
|
type BackupConfig,
|
||||||
|
type CronConfig,
|
||||||
|
} from "../types/services/backupConfig.js";
|
||||||
|
import { WorkflowType } from "../types/services/orchecstrator.js";
|
||||||
|
import { doBackupWorkflow } from "./orchestrator.js";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import { getStatus, setStatus } from "../tools/status.js";
|
||||||
|
import logger from "../config/winston.js";
|
||||||
|
|
||||||
|
let cronList: Map<string, CronJob>;
|
||||||
|
|
||||||
|
export function initCron(backupConfig: BackupConfig) {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
const fn = doBackupWorkflow;
|
||||||
|
if (cronList) {
|
||||||
|
stopAllCron(cronList);
|
||||||
|
}
|
||||||
|
cronList = new Map();
|
||||||
|
for (const cronItem of backupConfig.cron) {
|
||||||
|
try {
|
||||||
|
if (cronItem.mode == CronMode.DAILY) {
|
||||||
|
cronList.set(cronItem.id, getDailyCron(cronItem, fn));
|
||||||
|
} else if (cronItem.mode == CronMode.WEEKLY) {
|
||||||
|
cronList.set(cronItem.id, getWeeklyCron(cronItem, fn));
|
||||||
|
} else if (cronItem.mode == CronMode.MONTHLY) {
|
||||||
|
cronList.set(cronItem.id, getMonthlyCron(cronItem, fn));
|
||||||
|
} else if (cronItem.mode == CronMode.CUSTOM) {
|
||||||
|
cronList.set(cronItem.id, getCustomCron(cronItem, fn));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
logger.error(`Fail to init CRON ${cronItem.id} (${cronItem.mode})`);
|
||||||
|
stopAllCron(cronList);
|
||||||
|
rej(Error(cronItem.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const nextDate = getNextDate(cronList);
|
||||||
|
const status = getStatus();
|
||||||
|
status.next_backup = nextDate;
|
||||||
|
setStatus(status);
|
||||||
|
res(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextDate(cronList: Map<string, CronJob>) {
|
||||||
|
let nextDate: DateTime | undefined = undefined;
|
||||||
|
for (const item of cronList) {
|
||||||
|
const thisDate = item[1].nextDate();
|
||||||
|
if (!nextDate) {
|
||||||
|
nextDate = thisDate;
|
||||||
|
}
|
||||||
|
if (nextDate > thisDate) {
|
||||||
|
nextDate = thisDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAllCron(cronList: Map<string, CronJob>) {
|
||||||
|
for (const item of cronList) {
|
||||||
|
item[1].stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getDailyCron(
|
||||||
|
config: CronConfig,
|
||||||
|
fn: (type: WorkflowType) => Promise<void>
|
||||||
|
) {
|
||||||
|
const splited = (config.hour as string).split(":");
|
||||||
|
return new CronJob(
|
||||||
|
`${splited[1]} ${splited[0]} * * *`,
|
||||||
|
() => fn(WorkflowType.AUTO),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWeeklyCron(
|
||||||
|
config: CronConfig,
|
||||||
|
fn: (type: WorkflowType) => Promise<void>
|
||||||
|
) {
|
||||||
|
const splited = (config.hour as string).split(":");
|
||||||
|
return new CronJob(
|
||||||
|
`${splited[1]} ${splited[0]} * * ${config.weekday}`,
|
||||||
|
() => fn(WorkflowType.AUTO),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMonthlyCron(
|
||||||
|
config: CronConfig,
|
||||||
|
fn: (type: WorkflowType) => Promise<void>
|
||||||
|
) {
|
||||||
|
const splited = (config.hour as string).split(":");
|
||||||
|
return new CronJob(
|
||||||
|
`${splited[1]} ${splited[0]} ${config.monthDay} * *`,
|
||||||
|
() => fn(WorkflowType.AUTO),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCustomCron(
|
||||||
|
config: CronConfig,
|
||||||
|
fn: (type: WorkflowType) => Promise<void>
|
||||||
|
) {
|
||||||
|
return new CronJob(
|
||||||
|
config.custom as string,
|
||||||
|
() => fn(WorkflowType.AUTO),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
|
);
|
||||||
|
}
|
@ -21,7 +21,7 @@ export interface Status {
|
|||||||
last_success?: DateTime;
|
last_success?: DateTime;
|
||||||
last_try?: DateTime;
|
last_try?: DateTime;
|
||||||
};
|
};
|
||||||
next_backup?: string;
|
next_backup?: DateTime;
|
||||||
webdav: {
|
webdav: {
|
||||||
logged_in: boolean;
|
logged_in: boolean;
|
||||||
folder_created: boolean;
|
folder_created: boolean;
|
||||||
|
@ -47,6 +47,19 @@
|
|||||||
<v-col xl="6" lg="12" sm="6" cols="12">
|
<v-col xl="6" lg="12" sm="6" cols="12">
|
||||||
<div class="h-100 d-flex align-center">
|
<div class="h-100 d-flex align-center">
|
||||||
<span class="me-auto">Next</span>
|
<span class="me-auto">Next</span>
|
||||||
|
<v-chip
|
||||||
|
variant="elevated"
|
||||||
|
color="success"
|
||||||
|
prepend-icon="mdi-update"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
status?.next_backup
|
||||||
|
? DateTime.fromISO(status?.next_backup).toLocaleString(
|
||||||
|
DateTime.DATETIME_MED
|
||||||
|
)
|
||||||
|
: "Unknown"
|
||||||
|
}}
|
||||||
|
</v-chip>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
Loading…
Reference in New Issue
Block a user