diff --git a/.gitignore b/.gitignore index 5a9b0d5..2014478 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ Dockerfile # Config .autorestic.yml +.autorestic.lock .docker.yml \ No newline at end of file diff --git a/src/autorestic.ts b/src/autorestic.ts index 3046745..0289701 100644 --- a/src/autorestic.ts +++ b/src/autorestic.ts @@ -37,7 +37,7 @@ async function main() { config = init() // For dev - // return await handlers['check']([], { ...flags, all: true }) + // return await handlers['cron']([], { ...flags, all: true }) if (commands.length < 1 || commands[0] === 'help') return help() diff --git a/src/backend.ts b/src/backend.ts index 4b2e436..3b9e81b 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -2,7 +2,7 @@ import { Writer } from 'clitastic' import { config, VERBOSE } from './autorestic' import { Backend, Backends, Locations } from './types' -import { exec, pathRelativeToConfigFile } from './utils' +import { exec, pathRelativeToConfigFile, filterObjectByKey } from './utils' @@ -67,3 +67,9 @@ export const checkAndConfigureBackends = (backends?: Backends) => { for (const [name, backend] of Object.entries(backends)) checkAndConfigureBackend(name, backend) } + +export const checkAndConfigureBackendsForLocations = (locations: Locations) => { + checkAndConfigureBackends( + filterObjectByKey(config.backends, getBackendsFromLocations(locations)), + ) +} diff --git a/src/cron.ts b/src/cron.ts new file mode 100644 index 0000000..b70704f --- /dev/null +++ b/src/cron.ts @@ -0,0 +1,61 @@ +import fs from 'fs' + +import CronParser from 'cron-parser' + +import { config } from './autorestic' +import { checkAndConfigureBackendsForLocations } from './backend' +import { Location, Lockfile } from './types' +import { backupLocation } from './backup' +import { pathRelativeToConfigFile } from './utils' + + +const getLockFileName = () => { + const LOCK_FILE = '.autorestic.lock' + return pathRelativeToConfigFile(LOCK_FILE) +} + +const readLock = (): Lockfile => { + const name = getLockFileName() + let lock = {} + try { + lock = JSON.parse(fs.readFileSync(name, { encoding: 'utf-8' })) + } catch { } + return lock +} +const writeLock = (diff: Lockfile = {}) => { + const name = getLockFileName() + const newLock = Object.assign( + readLock(), + diff + ) + fs.writeFileSync(name, JSON.stringify(newLock, null, 2), { encoding: 'utf-8' }) +} + +const runCronForLocation = (name: string, location: Location) => { + const lock = readLock()[name] + const parsed = CronParser.parseExpression(location.cron || '') + const last = parsed.prev() + + if (!lock || last.toDate().getTime() > lock.lastRun) { + backupLocation(name, location) + writeLock({ + [name]: { + ...lock, + lastRun: Date.now() + } + }) + } else { + console.log(`${name.yellow} ▶ Skipping. Sheduled for: ${parsed.next().toString().underline.blue}`) + } +} + +export const runCron = () => { + const locationsWithCron = Object.entries(config.locations).filter(([name, { cron }]) => !!cron) + checkAndConfigureBackendsForLocations(Object.fromEntries(locationsWithCron)) + + console.log('\nRunning cron jobs'.underline.gray) + for (const [name, location] of locationsWithCron) + runCronForLocation(name, location) + + console.log('\nFinished!'.underline + ' 🎉') +} \ No newline at end of file diff --git a/src/handlers.ts b/src/handlers.ts index 6fdd09e..08ad022 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -6,8 +6,9 @@ import axios from 'axios' import { Writer } from 'clitastic' import { config, INSTALL_DIR, VERSION } from './autorestic' -import { checkAndConfigureBackends, getBackendsFromLocations, getEnvFromBackend } from './backend' +import { checkAndConfigureBackends, getEnvFromBackend, checkAndConfigureBackendsForLocations } from './backend' import { backupAll } from './backup' +import { runCron } from './cron' import { forgetAll } from './forget' import showAll from './info' import { restoreSingle } from './restore' @@ -72,14 +73,15 @@ const handlers: Handlers = { backup(args, flags) { checkIfResticIsAvailable() const locations: Locations = parseLocations(flags) - - checkAndConfigureBackends( - filterObjectByKey(config.backends, getBackendsFromLocations(locations)), - ) + checkAndConfigureBackendsForLocations(locations) backupAll(locations) console.log('\nFinished!'.underline + ' 🎉') }, + cron(args, flags) { + checkIfResticIsAvailable() + runCron() + }, restore(args, flags) { checkIfResticIsAvailable() @@ -93,10 +95,7 @@ const handlers: Handlers = { forget(args, flags) { checkIfResticIsAvailable() const locations: Locations = parseLocations(flags) - - checkAndConfigureBackends( - filterObjectByKey(config.backends, getBackendsFromLocations(locations)), - ) + checkAndConfigureBackendsForLocations(locations) forgetAll(locations, flags) console.log('\nFinished!'.underline + ' 🎉') diff --git a/src/types.ts b/src/types.ts index d7290e1..5cad816 100644 --- a/src/types.ts +++ b/src/types.ts @@ -92,4 +92,10 @@ export type Config = { backends: Backends } +export type Lockfile = { + [name: string]: { + lastRun: number + } +} + export type Flags = { [arg: string]: any }