mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2024-12-22 08:16:25 +00:00
config optional if not required for current operation
This commit is contained in:
parent
9dafe9d36a
commit
de27034b94
@ -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()
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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())
|
||||||
)
|
)
|
||||||
|
@ -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)) {
|
||||||
|
81
src/types.ts
81
src/types.ts
@ -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 }
|
94
src/utils.ts
94
src/utils.ts
@ -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')
|
||||||
|
Loading…
Reference in New Issue
Block a user