diff --git a/nextcloud_backup/backend/package.json b/nextcloud_backup/backend/package.json index e696def..974240e 100644 --- a/nextcloud_backup/backend/package.json +++ b/nextcloud_backup/backend/package.json @@ -15,7 +15,7 @@ "app-root-path": "3.1.0", "cookie-parser": "1.4.6", "cors": "^2.8.5", - "cron": "3.1.6", + "cron": "3.1.7", "debug": "4.3.4", "errorhandler": "^1.5.1", "express": "4.18.2", diff --git a/nextcloud_backup/backend/pnpm-lock.yaml b/nextcloud_backup/backend/pnpm-lock.yaml index 4aadaaa..24b102a 100644 --- a/nextcloud_backup/backend/pnpm-lock.yaml +++ b/nextcloud_backup/backend/pnpm-lock.yaml @@ -15,8 +15,8 @@ dependencies: specifier: ^2.8.5 version: 2.8.5 cron: - specifier: 3.1.6 - version: 3.1.6 + specifier: 3.1.7 + version: 3.1.7 debug: specifier: 4.3.4 version: 4.3.4(supports-color@5.5.0) @@ -400,13 +400,8 @@ packages: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true - /@types/luxon@3.3.8: - resolution: {integrity: sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==} - dev: false - /@types/luxon@3.4.2: resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} - dev: true /@types/mime@1.3.5: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -928,10 +923,10 @@ packages: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true - /cron@3.1.6: - resolution: {integrity: sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==} + /cron@3.1.7: + resolution: {integrity: sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==} dependencies: - '@types/luxon': 3.3.8 + '@types/luxon': 3.4.2 luxon: 3.4.4 dev: false diff --git a/nextcloud_backup/backend/src/postInit.ts b/nextcloud_backup/backend/src/postInit.ts index 8e47216..4ea3083 100644 --- a/nextcloud_backup/backend/src/postInit.ts +++ b/nextcloud_backup/backend/src/postInit.ts @@ -12,6 +12,8 @@ import { validateWebdavConfig, } from "./services/webdavConfigService.js"; import messageManager from "./tools/messageManager.js"; +import { initCron } from "./services/cronService.js"; +import { getBackupConfig } from "./services/backupConfigService.js"; function postInit() { logger.info(`Log level: ${process.env.LOG_LEVEL}`); @@ -39,34 +41,46 @@ function postInit() { ); const webdavConf = getWebdavConfig(); - validateWebdavConfig(webdavConf).then( - () => { - logger.info("Webdav config: " + kleur.green().bold("Go !")); - checkWebdavLogin(webdavConf).then( - () => { - logger.info("Webdav : " + kleur.green().bold("Go !")); - createBackupFolder(webdavConf).then( - () => { - logger.info("Webdav fodlers: " + kleur.green().bold("Go !")); - }, - (reason) => { - logger.error("Webdav folders: " + kleur.red().bold("FAIL !")); - logger.error(reason); - } - ); - }, - (reason) => { - logger.error("Webdav : " + kleur.red().bold("FAIL !")); - logger.error(reason); - } - ); - }, - (reason: Error) => { - logger.error("Webdav config: " + kleur.red().bold("FAIL !")); - logger.error(reason); - messageManager.error("Invalid webdav config", reason.message); - } - ); + validateWebdavConfig(webdavConf) + .then( + () => { + logger.info("Webdav config: " + kleur.green().bold("Go !")); + return checkWebdavLogin(webdavConf); + }, + (reason: Error) => { + logger.error("Webdav config: " + kleur.red().bold("FAIL !")); + logger.error(reason); + messageManager.error("Invalid webdav config", reason.message); + } + ) + .then( + () => { + logger.info("Webdav : " + kleur.green().bold("Go !")); + return createBackupFolder(webdavConf); + }, + (reason) => { + logger.error("Webdav : " + kleur.red().bold("FAIL !")); + logger.error(reason); + } + ) + .then( + () => { + logger.info("Webdav fodlers: " + kleur.green().bold("Go !")); + 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); // cronTools.init(); diff --git a/nextcloud_backup/backend/src/routes/config.ts b/nextcloud_backup/backend/src/routes/config.ts index 44f3da6..397139c 100644 --- a/nextcloud_backup/backend/src/routes/config.ts +++ b/nextcloud_backup/backend/src/routes/config.ts @@ -22,13 +22,24 @@ configRouter.get("/backup", (req, res) => { configRouter.put("/backup", (req, res) => { validateBackupConfig(req.body as BackupConfig) - .then(() => { - saveBackupConfig(req.body as BackupConfig); - res.status(204).send(); - }) - .catch((error: ValidationError) => { - res.status(400).json(error.details); - }); + .then( + () => { + return saveBackupConfig(req.body as BackupConfig); + }, + (error: ValidationError) => { + 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) => { diff --git a/nextcloud_backup/backend/src/services/backupConfigService.ts b/nextcloud_backup/backend/src/services/backupConfigService.ts index f0df8a8..3229bac 100644 --- a/nextcloud_backup/backend/src/services/backupConfigService.ts +++ b/nextcloud_backup/backend/src/services/backupConfigService.ts @@ -8,6 +8,7 @@ import { import backupConfigValidation from "../types/services/backupConfigValidation.js"; import { DateTime } from "luxon"; import { WorkflowType } from "../types/services/orchecstrator.js"; +import { initCron } from "./cronService.js"; const backupConfigPath = "/data/backupConfigV2.json"; @@ -20,13 +21,14 @@ export function validateBackupConfig(config: BackupConfig) { export function saveBackupConfig(config: BackupConfig) { fs.writeFileSync(backupConfigPath, JSON.stringify(config, undefined, 2)); + return initCron(config); } export function getBackupConfig(): BackupConfig { if (!fs.existsSync(backupConfigPath)) { logger.warn("Config file not found, creating default one !"); const defaultConfig = getBackupDefaultConfig(); - saveBackupConfig(defaultConfig); + saveBackupConfig(defaultConfig).catch(() => {}); return defaultConfig; } else { return JSON.parse( diff --git a/nextcloud_backup/backend/src/services/cronService.ts b/nextcloud_backup/backend/src/services/cronService.ts new file mode 100644 index 0000000..c4a7f38 --- /dev/null +++ b/nextcloud_backup/backend/src/services/cronService.ts @@ -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; + +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) { + 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) { + for (const item of cronList) { + item[1].stop(); + } +} +function getDailyCron( + config: CronConfig, + fn: (type: WorkflowType) => Promise +) { + 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 +) { + 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 +) { + 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 +) { + return new CronJob( + config.custom as string, + () => fn(WorkflowType.AUTO), + null, + true, + Intl.DateTimeFormat().resolvedOptions().timeZone + ); +} diff --git a/nextcloud_backup/backend/src/types/status.ts b/nextcloud_backup/backend/src/types/status.ts index f14b223..ba35242 100644 --- a/nextcloud_backup/backend/src/types/status.ts +++ b/nextcloud_backup/backend/src/types/status.ts @@ -21,7 +21,7 @@ export interface Status { last_success?: DateTime; last_try?: DateTime; }; - next_backup?: string; + next_backup?: DateTime; webdav: { logged_in: boolean; folder_created: boolean; diff --git a/nextcloud_backup/frontend/src/components/statusBar/BackupStatus.vue b/nextcloud_backup/frontend/src/components/statusBar/BackupStatus.vue index 090e7ec..4dd44cc 100644 --- a/nextcloud_backup/frontend/src/components/statusBar/BackupStatus.vue +++ b/nextcloud_backup/frontend/src/components/statusBar/BackupStatus.vue @@ -47,6 +47,19 @@
Next + + {{ + status?.next_backup + ? DateTime.fromISO(status?.next_backup).toLocaleString( + DateTime.DATETIME_MED + ) + : "Unknown" + }} +