config optional if not required for current operation

This commit is contained in:
cupcakearmy 2019-10-26 20:52:17 +02:00
parent 9dafe9d36a
commit de27034b94
7 changed files with 157 additions and 119 deletions

View File

@ -32,7 +32,7 @@ if (flags.version) {
process.exit(0) process.exit(0)
} }
export const config: Config = init() export const config = init()
function main() { function main() {
if (commands.length < 1) return help() if (commands.length < 1) return help()

View File

@ -2,56 +2,57 @@ import { Writer } from 'clitastic'
import { config, VERBOSE } from './autorestic' import { config, VERBOSE } from './autorestic'
import { Backend, Backends } from './types' import { Backend, Backends } from './types'
import { exec } from './utils' import { exec, ConfigError } from './utils'
const ALREADY_EXISTS = /(?=.*exists)(?=.*already)(?=.*config).*/ const ALREADY_EXISTS = /(?=.*exists)(?=.*already)(?=.*config).*/
export const getPathFromBackend = (backend: Backend): string => { export const getPathFromBackend = (backend: Backend): string => {
switch (backend.type) { switch (backend.type) {
case 'local': case 'local':
return backend.path return backend.path
case 'b2': case 'b2':
case 'azure': case 'azure':
case 'gs': case 'gs':
case 's3': case 's3':
return `${backend.type}:${backend.path}` return `${backend.type}:${backend.path}`
case 'sftp': case 'sftp':
case 'rest': case 'rest':
throw new Error(`Unsupported backend type: "${backend.type}"`) throw new Error(`Unsupported backend type: "${backend.type}"`)
default: default:
throw new Error(`Unknown backend type.`) throw new Error(`Unknown backend type.`)
} }
} }
export const getEnvFromBackend = (backend: Backend) => { export const getEnvFromBackend = (backend: Backend) => {
const { type, path, key, ...rest } = backend const { type, path, key, ...rest } = backend
return { return {
RESTIC_PASSWORD: key, RESTIC_PASSWORD: key,
RESTIC_REPOSITORY: getPathFromBackend(backend), RESTIC_REPOSITORY: getPathFromBackend(backend),
...rest, ...rest,
} }
} }
export const checkAndConfigureBackend = (name: string, backend: Backend) => { export const checkAndConfigureBackend = (name: string, backend: Backend) => {
const writer = new Writer(name.blue + ' : ' + 'Configuring... ⏳') const writer = new Writer(name.blue + ' : ' + 'Configuring... ⏳')
const env = getEnvFromBackend(backend) 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)) if (err.length > 0 && !ALREADY_EXISTS.test(err))
throw new Error(`Could not load the backend "${name}": ${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
}
export const checkAndConfigureBackends = (backends: Backends = config.backends) => { console.log('\nConfiguring Backends'.grey.underline)
console.log('\nConfiguring Backends'.grey.underline) for (const [name, backend] of Object.entries(backends))
for (const [name, backend] of Object.entries(backends)) checkAndConfigureBackend(name, backend)
checkAndConfigureBackend(name, backend) }
}

View File

@ -3,11 +3,12 @@ import { Writer } from 'clitastic'
import { config, VERBOSE } from './autorestic' import { config, VERBOSE } from './autorestic'
import { getEnvFromBackend } from './backend' import { getEnvFromBackend } from './backend'
import { Locations, Location } from './types' import { Locations, Location } from './types'
import { exec } from './utils' import { exec, ConfigError } from './utils'
import { CONFIG_FILE } from './config' import { CONFIG_FILE } from './config'
import { resolve, dirname } from 'path' import { resolve, dirname } from 'path'
export const backupSingle = (name: string, from: string, to: string) => { export const backupSingle = (name: string, from: string, to: string) => {
if (!config) throw ConfigError
const writer = new Writer(name + to.blue + ' : ' + 'Backing up... ⏳') const writer = new Writer(name + to.blue + ' : ' + 'Backing up... ⏳')
const backend = config.backends[to] const backend = config.backends[to]
const pathRelativeToConfigFile = resolve(dirname(CONFIG_FILE), from) const pathRelativeToConfigFile = resolve(dirname(CONFIG_FILE), from)
@ -34,7 +35,12 @@ export const backupLocation = (name: string, backup: Location) => {
} else backupSingle(display, backup.from, backup.to) } else backupSingle(display, backup.from, backup.to)
} }
export const backupAll = (backups: Locations = config.locations) => { export const backupAll = (backups?: Locations) => {
if (!backups) {
if (!config) throw ConfigError
backups = config.locations
}
console.log('\nBacking Up'.underline.grey) console.log('\nBacking Up'.underline.grey)
for (const [name, backup] of Object.entries(backups)) for (const [name, backup] of Object.entries(backups))
backupLocation(name, backup) backupLocation(name, backup)

View File

@ -69,8 +69,13 @@ const findConfigFile = (): string => {
export let CONFIG_FILE: string = '' export let CONFIG_FILE: string = ''
export const init = (): Config => { export const init = (): Config | undefined => {
CONFIG_FILE = findConfigFile() try {
CONFIG_FILE = findConfigFile()
} catch (e) {
return
}
const raw: Config = makeObjectKeysLowercase( const raw: Config = makeObjectKeysLowercase(
yaml.safeLoad(readFileSync(CONFIG_FILE).toString()) yaml.safeLoad(readFileSync(CONFIG_FILE).toString())
) )

View File

@ -15,6 +15,7 @@ import {
exec, exec,
filterObjectByKey, filterObjectByKey,
singleToArray, singleToArray,
ConfigError,
} from './utils' } from './utils'
export type Handlers = { export type Handlers = {
@ -22,6 +23,7 @@ export type Handlers = {
} }
const parseBackend = (flags: Flags): Backends => { const parseBackend = (flags: Flags): Backends => {
if (!config) throw ConfigError
if (!flags.all && !flags.backend) if (!flags.all && !flags.backend)
throw new Error( throw new Error(
'No backends specified.'.red + 'No backends specified.'.red +
@ -39,6 +41,7 @@ const parseBackend = (flags: Flags): Backends => {
} }
const parseLocations = (flags: Flags): Locations => { const parseLocations = (flags: Flags): Locations => {
if (!config) throw ConfigError
if (!flags.all && !flags.location) if (!flags.all && !flags.location)
throw new Error( throw new Error(
'No locations specified.'.red + 'No locations specified.'.red +
@ -64,6 +67,7 @@ const handlers: Handlers = {
checkAndConfigureBackends(backends) checkAndConfigureBackends(backends)
}, },
backup(args, flags) { backup(args, flags) {
if (!config) throw ConfigError
checkIfResticIsAvailable() checkIfResticIsAvailable()
const locations: Locations = parseLocations(flags) const locations: Locations = parseLocations(flags)
@ -79,6 +83,7 @@ const handlers: Handlers = {
console.log('\nFinished!'.underline + ' 🎉') console.log('\nFinished!'.underline + ' 🎉')
}, },
restore(args, flags) { restore(args, flags) {
if (!config) throw ConfigError
checkIfResticIsAvailable() checkIfResticIsAvailable()
const locations = parseLocations(flags) const locations = parseLocations(flags)
for (const [name, location] of Object.entries(locations)) { for (const [name, location] of Object.entries(locations)) {

View File

@ -1,70 +1,77 @@
type BackendLocal = { type BackendLocal = {
type: 'local', type: 'local'
key: string, key: string
path: string path: string
} }
type BackendSFTP = { type BackendSFTP = {
type: 'sftp', type: 'sftp'
key: string, key: string
path: string, path: string
password?: string, password?: string
} }
type BackendREST = { type BackendREST = {
type: 'rest', type: 'rest'
key: string, key: string
path: string, path: string
user?: string, user?: string
password?: string password?: string
} }
type BackendS3 = { type BackendS3 = {
type: 's3', type: 's3'
key: string, key: string
path: string, path: string
aws_access_key_id: string, aws_access_key_id: string
aws_secret_access_key: string, aws_secret_access_key: string
} }
type BackendB2 = { type BackendB2 = {
type: 'b2', type: 'b2'
key: string, key: string
path: string, path: string
b2_account_id: string, b2_account_id: string
b2_account_key: string b2_account_key: string
} }
type BackendAzure = { type BackendAzure = {
type: 'azure', type: 'azure'
key: string, key: string
path: string, path: string
azure_account_name: string, azure_account_name: string
azure_account_key: string azure_account_key: string
} }
type BackendGS = { type BackendGS = {
type: 'gs', type: 'gs'
key: string, key: string
path: string, path: string
google_project_id: string, google_project_id: string
google_application_credentials: string google_application_credentials: string
} }
export type Backend = BackendAzure | BackendB2 | BackendGS | BackendLocal | BackendREST | BackendS3 | BackendSFTP export type Backend =
| BackendAzure
| BackendB2
| BackendGS
| BackendLocal
| BackendREST
| BackendS3
| BackendSFTP
export type Backends = { [name: string]: Backend } export type Backends = { [name: string]: Backend }
export type Location = { export type Location = {
from: string, from: string
to: string | string[] to: string | string[]
} }
export type Locations = { [name: string]: Location } export type Locations = { [name: string]: Location }
export type Config = { export type Config = {
locations: Locations locations: Locations
backends: Backends backends: Backends
} }
export type Flags = { [arg: string]: any } export type Flags = { [arg: string]: any }

View File

@ -3,61 +3,75 @@ import { spawnSync, SpawnSyncOptions } from 'child_process'
import { randomBytes } from 'crypto' import { randomBytes } from 'crypto'
import { createWriteStream } from 'fs' import { createWriteStream } from 'fs'
export const exec = (command: string, args: string[], { env, ...rest }: SpawnSyncOptions = {}) => { export const exec = (
command: string,
args: string[],
{ env, ...rest }: SpawnSyncOptions = {}
) => {
const cmd = spawnSync(command, args, {
...rest,
env: {
...process.env,
...env,
},
})
const cmd = spawnSync(command, args, { const out = cmd.stdout && cmd.stdout.toString().trim()
...rest, const err = cmd.stderr && cmd.stderr.toString().trim()
env: {
...process.env,
...env,
},
})
const out = cmd.stdout && cmd.stdout.toString().trim() return { out, err }
const err = cmd.stderr && cmd.stderr.toString().trim()
return { out, err }
} }
export const checkIfResticIsAvailable = () => checkIfCommandIsAvailable( export const checkIfResticIsAvailable = () =>
'restic', checkIfCommandIsAvailable(
'Restic is not installed'.red + ' https://restic.readthedocs.io/en/latest/020_installation.html#stable-releases', 'restic',
) 'Restic is not installed'.red +
' https://restic.readthedocs.io/en/latest/020_installation.html#stable-releases'
)
export const checkIfCommandIsAvailable = (cmd: string, errorMsg?: string) => { export const checkIfCommandIsAvailable = (cmd: string, errorMsg?: string) => {
if (require('child_process').spawnSync(cmd).error) if (require('child_process').spawnSync(cmd).error)
throw new Error(errorMsg ? errorMsg : `"${errorMsg}" is not installed`.red) throw new Error(errorMsg ? errorMsg : `"${errorMsg}" is not installed`.red)
} }
export const makeObjectKeysLowercase = (object: Object): any => export const makeObjectKeysLowercase = (object: Object): any =>
Object.fromEntries( Object.fromEntries(
Object.entries(object) Object.entries(object).map(([key, value]) => [key.toLowerCase(), value])
.map(([key, value]) => [key.toLowerCase(), value]), )
)
export function rand(length = 32): string { export function rand(length = 32): string {
return randomBytes(length / 2).toString('hex') return randomBytes(length / 2).toString('hex')
} }
export const singleToArray = <T>(singleOrArray: T | T[]): T[] => Array.isArray(singleOrArray) ? singleOrArray : [singleOrArray] export const singleToArray = <T>(singleOrArray: T | T[]): T[] =>
Array.isArray(singleOrArray) ? singleOrArray : [singleOrArray]
export const filterObject = <T>(obj: { [key: string]: T }, filter: (item: [string, T]) => boolean): { [key: string]: T } => Object.fromEntries(Object.entries(obj).filter(filter)) export const filterObject = <T>(
obj: { [key: string]: T },
filter: (item: [string, T]) => boolean
): { [key: string]: T } =>
Object.fromEntries(Object.entries(obj).filter(filter))
export const filterObjectByKey = <T>(obj: { [key: string]: T }, keys: string[]) => filterObject(obj, ([key]) => keys.includes(key)) export const filterObjectByKey = <T>(
obj: { [key: string]: T },
keys: string[]
) => filterObject(obj, ([key]) => keys.includes(key))
export const downloadFile = async (url: string, to: string) => new Promise<void>(async res => { export const downloadFile = async (url: string, to: string) =>
const { data: file } = await axios({ new Promise<void>(async res => {
method: 'get', const { data: file } = await axios({
url: url, method: 'get',
responseType: 'stream', url: url,
}) responseType: 'stream',
})
const stream = createWriteStream(to) const stream = createWriteStream(to)
const writer = file.pipe(stream) const writer = file.pipe(stream)
writer.on('close', () => { writer.on('close', () => {
stream.close() stream.close()
res() res()
}) })
}) })
export const ConfigError = new Error('Config file not found')