formatting & trailing commas

This commit is contained in:
cupcakearmy 2019-12-03 23:37:55 +01:00
parent b68dc75053
commit 352754dad9
8 changed files with 500 additions and 481 deletions

View File

@ -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()

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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<string>(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<string>(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<string>(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<string>(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<string>()
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<string>()
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<string>()
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<string>()
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 <out dir>] Restore all or specified locations' +
'\n' +
'\n exec [-b, --backend] [-a, --all] <command> -- [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 <out dir>] Restore all or specified locations' +
'\n' +
'\n exec [-b, --backend] [-a, --all] <command> -- [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

View File

@ -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 }

View File

@ -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 = <T>(singleOrArray: T | T[]): T[] =>
Array.isArray(singleOrArray) ? singleOrArray : [singleOrArray]
Array.isArray(singleOrArray) ? singleOrArray : [singleOrArray]
export const filterObject = <T>(
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 = <T>(
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<void>(async res => {
const { data: file } = await axios({
method: 'get',
url: url,
responseType: 'stream',
})
new Promise<void>(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')