From 352754dad924315053103779179381e752a01776 Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Tue, 3 Dec 2019 23:37:55 +0100 Subject: [PATCH] formatting & trailing commas --- src/autorestic.ts | 40 +++-- src/backend.ts | 70 ++++---- src/backup.ts | 56 ++++--- src/config.ts | 113 +++++++------ src/forget.ts | 87 +++++----- src/handlers.ts | 417 +++++++++++++++++++++++----------------------- src/types.ts | 104 ++++++------ src/utils.ts | 94 ++++++----- 8 files changed, 500 insertions(+), 481 deletions(-) diff --git a/src/autorestic.ts b/src/autorestic.ts index f90d59f..2a97e41 100644 --- a/src/autorestic.ts +++ b/src/autorestic.ts @@ -5,23 +5,25 @@ import { init } from './config' import handlers, { error, help } from './handlers' import { Config } from './types' + + process.on('uncaughtException', err => { - console.log(err.message) - process.exit(1) + console.log(err.message) + process.exit(1) }) export const { _: commands, ...flags } = minimist(process.argv.slice(2), { - alias: { - c: 'config', - v: 'version', - h: 'help', - a: 'all', - l: 'location', - b: 'backend', - d: 'dry-run', - }, - boolean: ['a', 'd'], - string: ['l', 'b'], + alias: { + c: 'config', + v: 'version', + h: 'help', + a: 'all', + l: 'location', + b: 'backend', + d: 'dry-run', + }, + boolean: ['a', 'd'], + string: ['l', 'b'], }) export const VERSION = '0.6' @@ -30,12 +32,14 @@ export const VERBOSE = flags.verbose export const config = init() -function main() { - if (commands.length < 1) return help() - const command: string = commands[0] - const args: string[] = commands.slice(1) - ;(handlers[command] || error)(args, flags) +function main() { + if (commands.length < 1) return help() + + const command: string = commands[0] + const args: string[] = commands.slice(1) + ;(handlers[command] || error)(args, flags) } + main() diff --git a/src/backend.ts b/src/backend.ts index 437a23c..3c33d6a 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -4,55 +4,57 @@ import { config, VERBOSE } from './autorestic' import { Backend, Backends } from './types' import { exec, ConfigError } from './utils' + + const ALREADY_EXISTS = /(?=.*already)(?=.*config).*/ export const getPathFromBackend = (backend: Backend): string => { - switch (backend.type) { - case 'local': - return backend.path - case 'b2': - case 'azure': - case 'gs': - case 's3': - return `${backend.type}:${backend.path}` - case 'sftp': - case 'rest': - throw new Error(`Unsupported backend type: "${backend.type}"`) - default: - throw new Error(`Unknown backend type.`) - } + switch (backend.type) { + case 'local': + return backend.path + case 'b2': + case 'azure': + case 'gs': + case 's3': + return `${backend.type}:${backend.path}` + case 'sftp': + case 'rest': + throw new Error(`Unsupported backend type: "${backend.type}"`) + default: + throw new Error(`Unknown backend type.`) + } } export const getEnvFromBackend = (backend: Backend) => { - const { type, path, key, ...rest } = backend - return { - RESTIC_PASSWORD: key, - RESTIC_REPOSITORY: getPathFromBackend(backend), - ...rest, - } + const { type, path, key, ...rest } = backend + return { + RESTIC_PASSWORD: key, + RESTIC_REPOSITORY: getPathFromBackend(backend), + ...rest, + } } export const checkAndConfigureBackend = (name: string, backend: Backend) => { - const writer = new Writer(name.blue + ' : ' + 'Configuring... ⏳') - const env = getEnvFromBackend(backend) + const writer = new Writer(name.blue + ' : ' + 'Configuring... ⏳') + const env = getEnvFromBackend(backend) - const { out, err } = exec('restic', ['init'], { env }) + const { out, err } = exec('restic', ['init'], { env }) - if (err.length > 0 && !ALREADY_EXISTS.test(err)) - throw new Error(`Could not load the backend "${name}": ${err}`) + if (err.length > 0 && !ALREADY_EXISTS.test(err)) + throw new Error(`Could not load the backend "${name}": ${err}`) - if (VERBOSE && out.length > 0) console.log(out) + if (VERBOSE && out.length > 0) console.log(out) - writer.done(name.blue + ' : ' + 'Done ✓'.green) + writer.done(name.blue + ' : ' + 'Done ✓'.green) } export const checkAndConfigureBackends = (backends?: Backends) => { - if (!backends) { - if (!config) throw ConfigError - backends = config.backends - } + if (!backends) { + if (!config) throw ConfigError + backends = config.backends + } - console.log('\nConfiguring Backends'.grey.underline) - for (const [name, backend] of Object.entries(backends)) - checkAndConfigureBackend(name, backend) + console.log('\nConfiguring Backends'.grey.underline) + for (const [name, backend] of Object.entries(backends)) + checkAndConfigureBackend(name, backend) } diff --git a/src/backup.ts b/src/backup.ts index d71e304..334fb9d 100644 --- a/src/backup.ts +++ b/src/backup.ts @@ -5,42 +5,44 @@ import { getEnvFromBackend } from './backend' import { Locations, Location } from './types' import { exec, ConfigError, pathRelativeToConfigFile } from './utils' + + export const backupSingle = (name: string, from: string, to: string) => { - if (!config) throw ConfigError - const writer = new Writer(name + to.blue + ' : ' + 'Backing up... ⏳') - const backend = config.backends[to] + if (!config) throw ConfigError + const writer = new Writer(name + to.blue + ' : ' + 'Backing up... ⏳') + const backend = config.backends[to] - const path = pathRelativeToConfigFile(to) + const path = pathRelativeToConfigFile(to) - const cmd = exec('restic', ['backup', path], { - env: getEnvFromBackend(backend), - }) + const cmd = exec('restic', ['backup', path], { + env: getEnvFromBackend(backend), + }) - if (VERBOSE) console.log(cmd.out, cmd.err) - writer.done(name + to.blue + ' : ' + 'Done ✓'.green) + if (VERBOSE) console.log(cmd.out, cmd.err) + writer.done(name + to.blue + ' : ' + 'Done ✓'.green) } export const backupLocation = (name: string, backup: Location) => { - const display = name.yellow + ' ▶ ' - if (Array.isArray(backup.to)) { - let first = true - for (const t of backup.to) { - const nameOrBlankSpaces: string = first - ? display - : new Array(name.length + 3).fill(' ').join('') - backupSingle(nameOrBlankSpaces, backup.from, t) - if (first) first = false - } - } else backupSingle(display, backup.from, backup.to) + const display = name.yellow + ' ▶ ' + if (Array.isArray(backup.to)) { + let first = true + for (const t of backup.to) { + const nameOrBlankSpaces: string = first + ? display + : new Array(name.length + 3).fill(' ').join('') + backupSingle(nameOrBlankSpaces, backup.from, t) + if (first) first = false + } + } else backupSingle(display, backup.from, backup.to) } export const backupAll = (backups?: Locations) => { - if (!backups) { - if (!config) throw ConfigError - backups = config.locations - } + if (!backups) { + if (!config) throw ConfigError + backups = config.locations + } - console.log('\nBacking Up'.underline.grey) - for (const [name, backup] of Object.entries(backups)) - backupLocation(name, backup) + console.log('\nBacking Up'.underline.grey) + for (const [name, backup] of Object.entries(backups)) + backupLocation(name, backup) } diff --git a/src/config.ts b/src/config.ts index 74392ed..c63e3d0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,81 +6,84 @@ import { Backend, Config } from './types' import { makeObjectKeysLowercase, rand } from './utils' import { homedir } from 'os' + + export const normalizeAndCheckBackends = (config: Config) => { - config.backends = makeObjectKeysLowercase(config.backends) + config.backends = makeObjectKeysLowercase(config.backends) - for (const [name, { type, path, key, ...rest }] of Object.entries( - config.backends - )) { - if (!type || !path) - throw new Error( - `The backend "${name}" is missing some required attributes` - ) + for (const [name, { type, path, key, ...rest }] of Object.entries( + config.backends, + )) { + if (!type || !path) + throw new Error( + `The backend "${name}" is missing some required attributes`, + ) - const tmp: any = { - type, - path, - key: key || rand(128), - } - for (const [key, value] of Object.entries(rest)) - tmp[key.toUpperCase()] = value + const tmp: any = { + type, + path, + key: key || rand(128), + } + for (const [key, value] of Object.entries(rest)) + tmp[key.toUpperCase()] = value - config.backends[name] = tmp as Backend - } + config.backends[name] = tmp as Backend + } } export const normalizeAndCheckBackups = (config: Config) => { - config.locations = makeObjectKeysLowercase(config.locations) - const backends = Object.keys(config.backends) + config.locations = makeObjectKeysLowercase(config.locations) + const backends = Object.keys(config.backends) - const checkDestination = (backend: string, backup: string) => { - if (!backends.includes(backend)) - throw new Error(`Cannot find the backend "${backend}" for "${backup}"`) - } + const checkDestination = (backend: string, backup: string) => { + if (!backends.includes(backend)) + throw new Error(`Cannot find the backend "${backend}" for "${backup}"`) + } - for (const [name, { from, to, ...rest }] of Object.entries( - config.locations - )) { - if (!from || !to) - throw new Error( - `The backup "${name}" is missing some required attributes` - ) + for (const [name, { from, to, ...rest }] of Object.entries( + config.locations, + )) { + if (!from || !to) + throw new Error( + `The backup "${name}" is missing some required attributes`, + ) - if (Array.isArray(to)) for (const t of to) checkDestination(t, name) - else checkDestination(to, name) - } + if (Array.isArray(to)) for (const t of to) checkDestination(t, name) + else checkDestination(to, name) + } } const findConfigFile = (): string | undefined => { - const config = '.autorestic.yml' - const paths = [ - resolve(flags.config || ''), - resolve('./' + config), - homedir() + '/' + config, - ] - for (const path of paths) { - try { - const file = statSync(path) - if (file.isFile()) return path - } catch (e) {} - } + const config = '.autorestic.yml' + const paths = [ + resolve(flags.config || ''), + resolve('./' + config), + homedir() + '/' + config, + ] + for (const path of paths) { + try { + const file = statSync(path) + if (file.isFile()) return path + } catch (e) { + } + } } export let CONFIG_FILE: string = '' export const init = (): Config | undefined => { - const file = findConfigFile() - if (file) CONFIG_FILE = file - else return + const file = findConfigFile() + if (file) CONFIG_FILE = file + else return - const raw: Config = makeObjectKeysLowercase( - yaml.safeLoad(readFileSync(CONFIG_FILE).toString()) - ) + const raw: Config = makeObjectKeysLowercase( + yaml.safeLoad(readFileSync(CONFIG_FILE).toString()), + ) - normalizeAndCheckBackends(raw) - normalizeAndCheckBackups(raw) + normalizeAndCheckBackends(raw) + normalizeAndCheckBackups(raw) - writeFileSync(CONFIG_FILE, yaml.safeDump(raw)) + writeFileSync(CONFIG_FILE, yaml.safeDump(raw)) - return raw + return raw } diff --git a/src/forget.ts b/src/forget.ts index 305174d..ab5f944 100644 --- a/src/forget.ts +++ b/src/forget.ts @@ -5,56 +5,57 @@ import { getEnvFromBackend } from './backend' import { Locations, Location, ForgetPolicy, Flags } from './types' import { exec, ConfigError } from './utils' -export const forgetSingle = (dryRun: boolean, name: string, from: string, to: string, policy: ForgetPolicy) => { - if (!config) throw ConfigError - const writer = new Writer(name + to.blue + ' : ' + 'Removing old spnapshots… ⏳') - const backend = config.backends[to] - const flags = [] as any[] - for (const [name, value] of Object.entries(policy)) { - flags.push(`--keep-${name}`) - flags.push(value) - } - if (dryRun) { - flags.push('--dry-run') - } - const env = getEnvFromBackend(backend) - writer.replaceLn(name + to.blue + ' : ' + 'Forgeting old snapshots… ⏳') - const cmd = exec('restic', ['forget', '--path', from, '--prune', ...flags], { env }) - if (VERBOSE) console.log(cmd.out, cmd.err) - writer.done(name + to.blue + ' : ' + 'Done ✓'.green) + +export const forgetSingle = (dryRun: boolean, name: string, from: string, to: string, policy: ForgetPolicy) => { + if (!config) throw ConfigError + const writer = new Writer(name + to.blue + ' : ' + 'Removing old spnapshots… ⏳') + const backend = config.backends[to] + const flags = [] as any[] + for (const [name, value] of Object.entries(policy)) { + flags.push(`--keep-${name}`) + flags.push(value) + } + if (dryRun) { + flags.push('--dry-run') + } + const env = getEnvFromBackend(backend) + writer.replaceLn(name + to.blue + ' : ' + 'Forgeting old snapshots… ⏳') + const cmd = exec('restic', ['forget', '--path', from, '--prune', ...flags], { env }) + + if (VERBOSE) console.log(cmd.out, cmd.err) + writer.done(name + to.blue + ' : ' + 'Done ✓'.green) } export const forgetLocation = (dryRun: boolean, name: string, backup: Location, policy?: ForgetPolicy) => { - const display = name.yellow + ' ▶ ' - if (!policy) { - console.log(display + 'skipping, no policy declared') - } - else { - if (Array.isArray(backup.to)) { - let first = true - for (const t of backup.to) { - const nameOrBlankSpaces: string = first - ? display - : new Array(name.length + 3).fill(' ').join('') - forgetSingle(dryRun, nameOrBlankSpaces, backup.from, t, policy) - if (first) first = false - } - } else forgetSingle(dryRun, display, backup.from, backup.to, policy) - } + const display = name.yellow + ' ▶ ' + if (!policy) { + console.log(display + 'skipping, no policy declared') + } else { + if (Array.isArray(backup.to)) { + let first = true + for (const t of backup.to) { + const nameOrBlankSpaces: string = first + ? display + : new Array(name.length + 3).fill(' ').join('') + forgetSingle(dryRun, nameOrBlankSpaces, backup.from, t, policy) + if (first) first = false + } + } else forgetSingle(dryRun, display, backup.from, backup.to, policy) + } } export const forgetAll = (dryRun: boolean, backups?: Locations) => { - if (!config) throw ConfigError - if (!backups) { - backups = config.locations - } + if (!config) throw ConfigError + if (!backups) { + backups = config.locations + } - console.log('\nRemoving old shapshots according to policy'.underline.grey) - if (dryRun) console.log('Running in dry-run mode, not touching data\n'.yellow) + console.log('\nRemoving old shapshots according to policy'.underline.grey) + if (dryRun) console.log('Running in dry-run mode, not touching data\n'.yellow) - for (const [name, backup] of Object.entries(backups)) { - var policy = config.locations[name].keep - forgetLocation(dryRun, name, backup, policy) - } + for (const [name, backup] of Object.entries(backups)) { + var policy = config.locations[name].keep + forgetLocation(dryRun, name, backup, policy) + } } diff --git a/src/handlers.ts b/src/handlers.ts index 734f11d..e142c61 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -10,253 +10,256 @@ import { backupAll } from './backup' import { forgetAll } from './forget' import { Backends, Flags, Locations } from './types' import { - checkIfCommandIsAvailable, - checkIfResticIsAvailable, - downloadFile, - exec, - filterObjectByKey, - singleToArray, - ConfigError, + checkIfCommandIsAvailable, + checkIfResticIsAvailable, + downloadFile, + exec, + filterObjectByKey, + singleToArray, + ConfigError, } from './utils' + + export type Handlers = { - [command: string]: (args: string[], flags: Flags) => void + [command: string]: (args: string[], flags: Flags) => void } const parseBackend = (flags: Flags): Backends => { - if (!config) throw ConfigError - if (!flags.all && !flags.backend) - throw new Error( - 'No backends specified.'.red + - '\n--all [-a]\t\t\t\tCheck all.' + - '\n--backend [-b] myBackend\t\tSpecify one or more backend' - ) - if (flags.all) return config.backends - else { - const backends = singleToArray(flags.backend) - for (const backend of backends) - if (!config.backends[backend]) - throw new Error('Invalid backend: '.red + backend) - return filterObjectByKey(config.backends, backends) - } + if (!config) throw ConfigError + if (!flags.all && !flags.backend) + throw new Error( + 'No backends specified.'.red + + '\n--all [-a]\t\t\t\tCheck all.' + + '\n--backend [-b] myBackend\t\tSpecify one or more backend', + ) + if (flags.all) return config.backends + else { + const backends = singleToArray(flags.backend) + for (const backend of backends) + if (!config.backends[backend]) + throw new Error('Invalid backend: '.red + backend) + return filterObjectByKey(config.backends, backends) + } } const parseLocations = (flags: Flags): Locations => { - if (!config) throw ConfigError - if (!flags.all && !flags.location) - throw new Error( - 'No locations specified.'.red + - '\n--all [-a]\t\t\t\tBackup all.' + - '\n--location [-l] site1\t\t\tSpecify one or more locations' - ) + if (!config) throw ConfigError + if (!flags.all && !flags.location) + throw new Error( + 'No locations specified.'.red + + '\n--all [-a]\t\t\t\tBackup all.' + + '\n--location [-l] site1\t\t\tSpecify one or more locations', + ) - if (flags.all) { - return config.locations - } else { - const locations = singleToArray(flags.location) - for (const location of locations) - if (!config.locations[location]) - throw new Error('Invalid location: '.red + location) - return filterObjectByKey(config.locations, locations) - } + if (flags.all) { + return config.locations + } else { + const locations = singleToArray(flags.location) + for (const location of locations) + if (!config.locations[location]) + throw new Error('Invalid location: '.red + location) + return filterObjectByKey(config.locations, locations) + } } const handlers: Handlers = { - check(args, flags) { - checkIfResticIsAvailable() - const backends = parseBackend(flags) - checkAndConfigureBackends(backends) - }, - backup(args, flags) { - if (!config) throw ConfigError - checkIfResticIsAvailable() - const locations: Locations = parseLocations(flags) + check(args, flags) { + checkIfResticIsAvailable() + const backends = parseBackend(flags) + checkAndConfigureBackends(backends) + }, + backup(args, flags) { + if (!config) throw ConfigError + checkIfResticIsAvailable() + const locations: Locations = parseLocations(flags) - const backends = new Set() - for (const to of Object.values(locations).map(location => location.to)) - Array.isArray(to) ? to.forEach(t => backends.add(t)) : backends.add(to) + const backends = new Set() + for (const to of Object.values(locations).map(location => location.to)) + Array.isArray(to) ? to.forEach(t => backends.add(t)) : backends.add(to) - checkAndConfigureBackends( - filterObjectByKey(config.backends, Array.from(backends)) - ) - backupAll(locations) + checkAndConfigureBackends( + filterObjectByKey(config.backends, Array.from(backends)), + ) + backupAll(locations) - console.log('\nFinished!'.underline + ' 🎉') - }, - restore(args, flags) { - if (!config) throw ConfigError - checkIfResticIsAvailable() - const locations = parseLocations(flags) - for (const [name, location] of Object.entries(locations)) { - const w = new Writer(name.green + `\t\tRestoring... ⏳`) - const env = getEnvFromBackend( - config.backends[ - Array.isArray(location.to) ? location.to[0] : location.to - ] - ) + console.log('\nFinished!'.underline + ' 🎉') + }, + restore(args, flags) { + if (!config) throw ConfigError + checkIfResticIsAvailable() + const locations = parseLocations(flags) + for (const [name, location] of Object.entries(locations)) { + const w = new Writer(name.green + `\t\tRestoring... ⏳`) + const env = getEnvFromBackend( + config.backends[ + Array.isArray(location.to) ? location.to[0] : location.to + ], + ) - exec( - 'restic', - ['restore', 'latest', '--path', resolve(location.from), ...args], - { env } - ) - w.done(name.green + '\t\tDone 🎉') - } - }, - forget(args, flags) { - if (!config) throw ConfigError - checkIfResticIsAvailable() - const locations: Locations = parseLocations(flags) + exec( + 'restic', + ['restore', 'latest', '--path', resolve(location.from), ...args], + { env }, + ) + w.done(name.green + '\t\tDone 🎉') + } + }, + forget(args, flags) { + if (!config) throw ConfigError + checkIfResticIsAvailable() + const locations: Locations = parseLocations(flags) - const backends = new Set() - for (const to of Object.values(locations).map(location => location.to)) - Array.isArray(to) ? to.forEach(t => backends.add(t)) : backends.add(to) + const backends = new Set() + for (const to of Object.values(locations).map(location => location.to)) + Array.isArray(to) ? to.forEach(t => backends.add(t)) : backends.add(to) - checkAndConfigureBackends( - filterObjectByKey(config.backends, Array.from(backends)) - ) - forgetAll(flags['dry-run'], locations) + checkAndConfigureBackends( + filterObjectByKey(config.backends, Array.from(backends)), + ) + forgetAll(flags['dry-run'], locations) - console.log('\nFinished!'.underline + ' 🎉') - }, - exec(args, flags) { - checkIfResticIsAvailable() - const backends = parseBackend(flags) - for (const [name, backend] of Object.entries(backends)) { - console.log(`\n${name}:\n`.grey.underline) - const env = getEnvFromBackend(backend) + console.log('\nFinished!'.underline + ' 🎉') + }, + exec(args, flags) { + checkIfResticIsAvailable() + const backends = parseBackend(flags) + for (const [name, backend] of Object.entries(backends)) { + console.log(`\n${name}:\n`.grey.underline) + const env = getEnvFromBackend(backend) - const { out, err } = exec('restic', args, { env }) - console.log(out, err) - } - }, - async install() { - try { - checkIfResticIsAvailable() - console.log('Restic is already installed') - return - } catch (e) {} + const { out, err } = exec('restic', args, { env }) + console.log(out, err) + } + }, + async install() { + try { + checkIfResticIsAvailable() + console.log('Restic is already installed') + return + } catch (e) { + } - const w = new Writer('Checking latest version... ⏳') - checkIfCommandIsAvailable('bzip2') - const { data: json } = await axios({ - method: 'get', - url: 'https://api.github.com/repos/restic/restic/releases/latest', - responseType: 'json', - }) + const w = new Writer('Checking latest version... ⏳') + checkIfCommandIsAvailable('bzip2') + const { data: json } = await axios({ + method: 'get', + url: 'https://api.github.com/repos/restic/restic/releases/latest', + responseType: 'json', + }) - const archMap: { [a: string]: string } = { - x32: '386', - x64: 'amd64', - } + const archMap: { [a: string]: string } = { + x32: '386', + x64: 'amd64', + } - w.replaceLn('Downloading binary... 🌎') - const name = `${json.name.replace(' ', '_')}_${process.platform}_${ - archMap[process.arch] - }.bz2` - const dl = json.assets.find((asset: any) => asset.name === name) - if (!dl) - return console.log( - 'Cannot get the right binary.'.red, - 'Please see https://bit.ly/2Y1Rzai' - ) + w.replaceLn('Downloading binary... 🌎') + const name = `${json.name.replace(' ', '_')}_${process.platform}_${ + archMap[process.arch] + }.bz2` + const dl = json.assets.find((asset: any) => asset.name === name) + if (!dl) + return console.log( + 'Cannot get the right binary.'.red, + 'Please see https://bit.ly/2Y1Rzai', + ) - const tmp = join(tmpdir(), name) - const extracted = tmp.slice(0, -4) //without the .bz2 + const tmp = join(tmpdir(), name) + const extracted = tmp.slice(0, -4) //without the .bz2 - await downloadFile(dl.browser_download_url, tmp) + await downloadFile(dl.browser_download_url, tmp) - // TODO: Native bz2 - // Decompress - w.replaceLn('Decompressing binary... 📦') - exec('bzip2', ['-dk', tmp]) - unlinkSync(tmp) + // TODO: Native bz2 + // Decompress + w.replaceLn('Decompressing binary... 📦') + exec('bzip2', ['-dk', tmp]) + unlinkSync(tmp) - w.replaceLn(`Moving to ${INSTALL_DIR} 🚙`) - exec('chmod', ['+x', extracted]) - exec('mv', [extracted, INSTALL_DIR + '/restic']) + w.replaceLn(`Moving to ${INSTALL_DIR} 🚙`) + exec('chmod', ['+x', extracted]) + exec('mv', [extracted, INSTALL_DIR + '/restic']) - w.done( - `\nFinished! restic is installed under: ${INSTALL_DIR}`.underline + ' 🎉' - ) - }, - uninstall() { - for (const bin of ['restic', 'autorestic']) - try { - unlinkSync(INSTALL_DIR + '/' + bin) - console.log(`Finished! ${bin} was uninstalled`) - } catch (e) { - console.log(`${bin} is already uninstalled`.red) - } - }, - async update() { - checkIfResticIsAvailable() - const w = new Writer('Checking for latest restic version... ⏳') - exec('restic', ['self-update']) + w.done( + `\nFinished! restic is installed under: ${INSTALL_DIR}`.underline + ' 🎉', + ) + }, + uninstall() { + for (const bin of ['restic', 'autorestic']) + try { + unlinkSync(INSTALL_DIR + '/' + bin) + console.log(`Finished! ${bin} was uninstalled`) + } catch (e) { + console.log(`${bin} is already uninstalled`.red) + } + }, + async update() { + checkIfResticIsAvailable() + const w = new Writer('Checking for latest restic version... ⏳') + exec('restic', ['self-update']) - w.replaceLn('Checking for latest autorestic version... ⏳') - const { data: json } = await axios({ - method: 'get', - url: - 'https://api.github.com/repos/cupcakearmy/autorestic/releases/latest', - responseType: 'json', - }) + w.replaceLn('Checking for latest autorestic version... ⏳') + const { data: json } = await axios({ + method: 'get', + url: + 'https://api.github.com/repos/cupcakearmy/autorestic/releases/latest', + responseType: 'json', + }) - if (json.tag_name != VERSION) { - const platformMap: { [key: string]: string } = { - darwin: 'macos', - } + if (json.tag_name != VERSION) { + const platformMap: { [key: string]: string } = { + darwin: 'macos', + } - const name = `autorestic_${platformMap[process.platform] || - process.platform}_${process.arch}` - const dl = json.assets.find((asset: any) => asset.name === name) + const name = `autorestic_${platformMap[process.platform] || + process.platform}_${process.arch}` + const dl = json.assets.find((asset: any) => asset.name === name) - const to = INSTALL_DIR + '/autorestic' - w.replaceLn('Downloading binary... 🌎') - await downloadFile(dl.browser_download_url, to) + const to = INSTALL_DIR + '/autorestic' + w.replaceLn('Downloading binary... 🌎') + await downloadFile(dl.browser_download_url, to) - exec('chmod', ['+x', to]) - } + exec('chmod', ['+x', to]) + } - w.done('All up to date! 🚀') - }, - version() { - console.log('version'.grey, VERSION) - }, + w.done('All up to date! 🚀') + }, + version() { + console.log('version'.grey, VERSION) + }, } export const help = () => { - console.log( - '\nAutorestic'.blue + - ` - ${VERSION} - Easy Restic CLI Utility` + - '\n' + - '\nOptions:'.yellow + - `\n -c, --config Specify config file. Default: .autorestic.yml` + - '\n' + - '\nCommands:'.yellow + - '\n check [-b, --backend] [-a, --all] Check backends' + - '\n backup [-l, --location] [-a, --all] Backup all or specified locations' + - '\n forget [-l, --location] [-a, --all] [--dry-run] Forget old snapshots according to declared policies' + - '\n restore [-l, --location] [-- --target ] Restore all or specified locations' + - '\n' + - '\n exec [-b, --backend] [-a, --all] -- [native options] Execute native restic command' + - '\n' + - '\n install install restic' + - '\n uninstall uninstall restic' + - '\n update update restic' + - '\n help Show help' + - '\n' + - '\nExamples: '.yellow + - 'https://git.io/fjVbg' + - '\n' - ) + console.log( + '\nAutorestic'.blue + + ` - ${VERSION} - Easy Restic CLI Utility` + + '\n' + + '\nOptions:'.yellow + + `\n -c, --config Specify config file. Default: .autorestic.yml` + + '\n' + + '\nCommands:'.yellow + + '\n check [-b, --backend] [-a, --all] Check backends' + + '\n backup [-l, --location] [-a, --all] Backup all or specified locations' + + '\n forget [-l, --location] [-a, --all] [--dry-run] Forget old snapshots according to declared policies' + + '\n restore [-l, --location] [-- --target ] Restore all or specified locations' + + '\n' + + '\n exec [-b, --backend] [-a, --all] -- [native options] Execute native restic command' + + '\n' + + '\n install install restic' + + '\n uninstall uninstall restic' + + '\n update update restic' + + '\n help Show help' + + '\n' + + '\nExamples: '.yellow + + 'https://git.io/fjVbg' + + '\n', + ) } export const error = () => { - help() - console.log( - `Invalid Command:`.red.underline, - `${process.argv.slice(2).join(' ')}` - ) + help() + console.log( + `Invalid Command:`.red.underline, + `${process.argv.slice(2).join(' ')}`, + ) } export default handlers diff --git a/src/types.ts b/src/types.ts index 13ef81c..9b95928 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,89 +1,89 @@ type BackendLocal = { - type: 'local' - key: string - path: string + type: 'local' + key: string + path: string } type BackendSFTP = { - type: 'sftp' - key: string - path: string - password?: string + type: 'sftp' + key: string + path: string + password?: string } type BackendREST = { - type: 'rest' - key: string - path: string - user?: string - password?: string + type: 'rest' + key: string + path: string + user?: string + password?: string } type BackendS3 = { - type: 's3' - key: string - path: string - aws_access_key_id: string - aws_secret_access_key: string + type: 's3' + key: string + path: string + aws_access_key_id: string + aws_secret_access_key: string } type BackendB2 = { - type: 'b2' - key: string - path: string - b2_account_id: string - b2_account_key: string + type: 'b2' + key: string + path: string + b2_account_id: string + b2_account_key: string } type BackendAzure = { - type: 'azure' - key: string - path: string - azure_account_name: string - azure_account_key: string + type: 'azure' + key: string + path: string + azure_account_name: string + azure_account_key: string } type BackendGS = { - type: 'gs' - key: string - path: string - google_project_id: string - google_application_credentials: string + type: 'gs' + key: string + path: string + google_project_id: string + google_application_credentials: string } export type Backend = - | BackendAzure - | BackendB2 - | BackendGS - | BackendLocal - | BackendREST - | BackendS3 - | BackendSFTP + | BackendAzure + | BackendB2 + | BackendGS + | BackendLocal + | BackendREST + | BackendS3 + | BackendSFTP export type Backends = { [name: string]: Backend } export type ForgetPolicy = { - last?: number, - hourly?: number, - daily?: number, - weekly?: number, - monthly?: number, - yearly?: number, - within?: string, - tags?: string[], + last?: number, + hourly?: number, + daily?: number, + weekly?: number, + monthly?: number, + yearly?: number, + within?: string, + tags?: string[], } export type Location = { - from: string - to: string | string[] - keep?: ForgetPolicy + from: string + to: string | string[] + keep?: ForgetPolicy } export type Locations = { [name: string]: Location } export type Config = { - locations: Locations - backends: Backends + locations: Locations + backends: Backends } export type Flags = { [arg: string]: any } diff --git a/src/utils.ts b/src/utils.ts index d39a316..ed90bcb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,80 +5,84 @@ import { createWriteStream } from 'fs' import { isAbsolute, resolve, dirname } from 'path' import { CONFIG_FILE } from './config' + + export const exec = ( - command: string, - args: string[], - { env, ...rest }: SpawnSyncOptions = {} + command: string, + args: string[], + { env, ...rest }: SpawnSyncOptions = {}, ) => { - const cmd = spawnSync(command, args, { - ...rest, - env: { - ...process.env, - ...env, - }, - }) + const cmd = spawnSync(command, args, { + ...rest, + env: { + ...process.env, + ...env, + }, + }) - const out = cmd.stdout && cmd.stdout.toString().trim() - const err = cmd.stderr && cmd.stderr.toString().trim() + const out = cmd.stdout && cmd.stdout.toString().trim() + const err = cmd.stderr && cmd.stderr.toString().trim() - return { out, err } + return { out, err } } export const checkIfResticIsAvailable = () => - checkIfCommandIsAvailable( - 'restic', - 'Restic is not installed'.red + - ' https://restic.readthedocs.io/en/latest/020_installation.html#stable-releases' - ) + checkIfCommandIsAvailable( + 'restic', + 'Restic is not installed'.red + + ' https://restic.readthedocs.io/en/latest/020_installation.html#stable-releases', + ) export const checkIfCommandIsAvailable = (cmd: string, errorMsg?: string) => { - if (require('child_process').spawnSync(cmd).error) - throw new Error(errorMsg ? errorMsg : `"${errorMsg}" is not installed`.red) + if (require('child_process').spawnSync(cmd).error) + throw new Error(errorMsg ? errorMsg : `"${errorMsg}" is not installed`.red) } export const makeObjectKeysLowercase = (object: Object): any => - Object.fromEntries( - Object.entries(object).map(([key, value]) => [key.toLowerCase(), value]) - ) + Object.fromEntries( + Object.entries(object).map(([key, value]) => [key.toLowerCase(), value]), + ) + export function rand(length = 32): string { - return randomBytes(length / 2).toString('hex') + return randomBytes(length / 2).toString('hex') } + export const singleToArray = (singleOrArray: T | T[]): T[] => - Array.isArray(singleOrArray) ? singleOrArray : [singleOrArray] + Array.isArray(singleOrArray) ? singleOrArray : [singleOrArray] export const filterObject = ( - obj: { [key: string]: T }, - filter: (item: [string, T]) => boolean + obj: { [key: string]: T }, + filter: (item: [string, T]) => boolean, ): { [key: string]: T } => - Object.fromEntries(Object.entries(obj).filter(filter)) + Object.fromEntries(Object.entries(obj).filter(filter)) export const filterObjectByKey = ( - obj: { [key: string]: T }, - keys: string[] + obj: { [key: string]: T }, + keys: string[], ) => filterObject(obj, ([key]) => keys.includes(key)) export const downloadFile = async (url: string, to: string) => - new Promise(async res => { - const { data: file } = await axios({ - method: 'get', - url: url, - responseType: 'stream', - }) + new Promise(async res => { + const { data: file } = await axios({ + method: 'get', + url: url, + responseType: 'stream', + }) - const stream = createWriteStream(to) + const stream = createWriteStream(to) - const writer = file.pipe(stream) - writer.on('close', () => { - stream.close() - res() - }) - }) + const writer = file.pipe(stream) + writer.on('close', () => { + stream.close() + res() + }) + }) // Check if is an absolute path, otherwise get the path relative to the config file export const pathRelativeToConfigFile = (path: string): string => isAbsolute(path) - ? path - : resolve(dirname(CONFIG_FILE), path) + ? path + : resolve(dirname(CONFIG_FILE), path) export const ConfigError = new Error('Config file not found')