This commit is contained in:
cupcakearmy 2019-10-26 20:07:19 +02:00
parent 3c0ebdfb4a
commit 3d1e28e574

View File

@ -4,213 +4,233 @@ import { unlinkSync } from 'fs'
import { tmpdir } from 'os' import { tmpdir } from 'os'
import { join, resolve } from 'path' import { join, resolve } from 'path'
import { config, CONFIG_FILE, INSTALL_DIR, VERSION } from './autorestic' import { config, INSTALL_DIR, VERSION } from './autorestic'
import { checkAndConfigureBackends, getEnvFromBackend } from './backend' import { checkAndConfigureBackends, getEnvFromBackend } from './backend'
import { backupAll } from './backup' import { backupAll } from './backup'
import { Backends, Flags, Locations } from './types' import { Backends, Flags, Locations } from './types'
import { import {
checkIfCommandIsAvailable, checkIfCommandIsAvailable,
checkIfResticIsAvailable, checkIfResticIsAvailable,
downloadFile, downloadFile,
exec, exec,
filterObjectByKey, filterObjectByKey,
singleToArray, singleToArray,
} from './utils' } from './utils'
export type Handlers = { [command: string]: (args: string[], flags: Flags) => void } export type Handlers = {
[command: string]: (args: string[], flags: Flags) => void
}
const parseBackend = (flags: Flags): Backends => { const parseBackend = (flags: Flags): Backends => {
if (!flags.all && !flags.backend) if (!flags.all && !flags.backend)
throw new Error('No backends specified.'.red throw new Error(
+ '\n--all [-a]\t\t\t\tCheck all.' 'No backends specified.'.red +
+ '\n--backend [-b] myBackend\t\tSpecify one or more backend', '\n--all [-a]\t\t\t\tCheck all.' +
) '\n--backend [-b] myBackend\t\tSpecify one or more backend'
if (flags.all) )
return config.backends if (flags.all) return config.backends
else { else {
const backends = singleToArray<string>(flags.backend) const backends = singleToArray<string>(flags.backend)
for (const backend of backends) for (const backend of backends)
if (!config.backends[backend]) if (!config.backends[backend])
throw new Error('Invalid backend: '.red + backend) throw new Error('Invalid backend: '.red + backend)
return filterObjectByKey(config.backends, backends) return filterObjectByKey(config.backends, backends)
} }
} }
const parseLocations = (flags: Flags): Locations => { const parseLocations = (flags: Flags): Locations => {
if (!flags.all && !flags.location) if (!flags.all && !flags.location)
throw new Error('No locations specified.'.red throw new Error(
+ '\n--all [-a]\t\t\t\tBackup all.' 'No locations specified.'.red +
+ '\n--location [-l] site1\t\t\tSpecify one or more locations', '\n--all [-a]\t\t\t\tBackup all.' +
) '\n--location [-l] site1\t\t\tSpecify one or more locations'
)
if (flags.all) { if (flags.all) {
return config.locations return config.locations
} else { } else {
const locations = singleToArray<string>(flags.location) const locations = singleToArray<string>(flags.location)
for (const location of locations) for (const location of locations)
if (!config.locations[location]) if (!config.locations[location])
throw new Error('Invalid location: '.red + location) throw new Error('Invalid location: '.red + location)
return filterObjectByKey(config.locations, locations) return filterObjectByKey(config.locations, locations)
} }
} }
const handlers: Handlers = { const handlers: Handlers = {
check(args, flags) { check(args, flags) {
checkIfResticIsAvailable() checkIfResticIsAvailable()
const backends = parseBackend(flags) const backends = parseBackend(flags)
checkAndConfigureBackends(backends) checkAndConfigureBackends(backends)
}, },
backup(args, flags) { backup(args, flags) {
checkIfResticIsAvailable() checkIfResticIsAvailable()
const locations: Locations = parseLocations(flags) const locations: Locations = parseLocations(flags)
const backends = new Set<string>() const backends = new Set<string>()
for (const to of Object.values(locations).map(location => location.to)) for (const to of Object.values(locations).map(location => location.to))
Array.isArray(to) ? to.forEach(t => backends.add(t)) : backends.add(to) Array.isArray(to) ? to.forEach(t => backends.add(t)) : backends.add(to)
checkAndConfigureBackends(filterObjectByKey(config.backends, Array.from(backends))) checkAndConfigureBackends(
backupAll(locations) filterObjectByKey(config.backends, Array.from(backends))
)
backupAll(locations)
console.log('\nFinished!'.underline + ' 🎉') console.log('\nFinished!'.underline + ' 🎉')
}, },
restore(args, flags) { restore(args, flags) {
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)) {
const w = new Writer(name.green + `\t\tRestoring... ⏳`) const w = new Writer(name.green + `\t\tRestoring... ⏳`)
const env = getEnvFromBackend(config.backends[Array.isArray(location.to) ? location.to[0] : location.to]) const env = getEnvFromBackend(
config.backends[
Array.isArray(location.to) ? location.to[0] : location.to
]
)
exec( exec(
'restic', 'restic',
['restore', 'latest', '--path', resolve(location.from), ...args], ['restore', 'latest', '--path', resolve(location.from), ...args],
{ env }, { env }
) )
w.done(name.green + '\t\tDone 🎉') w.done(name.green + '\t\tDone 🎉')
} }
}, },
exec(args, flags) { exec(args, flags) {
checkIfResticIsAvailable() checkIfResticIsAvailable()
const backends = parseBackend(flags) const backends = parseBackend(flags)
for (const [name, backend] of Object.entries(backends)) { for (const [name, backend] of Object.entries(backends)) {
console.log(`\n${name}:\n`.grey.underline) console.log(`\n${name}:\n`.grey.underline)
const env = getEnvFromBackend(backend) const env = getEnvFromBackend(backend)
const { out, err } = exec('restic', args, { env }) const { out, err } = exec('restic', args, { env })
console.log(out, err) console.log(out, err)
} }
}, },
async install() { async install() {
try { try {
checkIfResticIsAvailable() checkIfResticIsAvailable()
console.log('Restic is already installed') console.log('Restic is already installed')
return return
} catch (e) { } catch (e) {}
}
const w = new Writer('Checking latest version... ⏳') const w = new Writer('Checking latest version... ⏳')
checkIfCommandIsAvailable('bzip2') checkIfCommandIsAvailable('bzip2')
const { data: json } = await axios({ const { data: json } = await axios({
method: 'get', method: 'get',
url: 'https://api.github.com/repos/restic/restic/releases/latest', url: 'https://api.github.com/repos/restic/restic/releases/latest',
responseType: 'json', responseType: 'json',
}) })
const archMap: { [a: string]: string } = { const archMap: { [a: string]: string } = {
'x32': '386', x32: '386',
'x64': 'amd64', x64: 'amd64',
} }
w.replaceLn('Downloading binary... 🌎') w.replaceLn('Downloading binary... 🌎')
const name = `${json.name.replace(' ', '_')}_${process.platform}_${archMap[process.arch]}.bz2` const name = `${json.name.replace(' ', '_')}_${process.platform}_${
const dl = json.assets.find((asset: any) => asset.name === name) archMap[process.arch]
if (!dl) return console.log( }.bz2`
'Cannot get the right binary.'.red, const dl = json.assets.find((asset: any) => asset.name === name)
'Please see https://bit.ly/2Y1Rzai', if (!dl)
) return console.log(
'Cannot get the right binary.'.red,
'Please see https://bit.ly/2Y1Rzai'
)
const tmp = join(tmpdir(), name) const tmp = join(tmpdir(), name)
const extracted = tmp.slice(0, -4) //without the .bz2 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 // TODO: Native bz2
// Decompress // Decompress
w.replaceLn('Decompressing binary... 📦') w.replaceLn('Decompressing binary... 📦')
exec('bzip2', ['-dk', tmp]) exec('bzip2', ['-dk', tmp])
unlinkSync(tmp) unlinkSync(tmp)
w.replaceLn(`Moving to ${INSTALL_DIR} 🚙`) w.replaceLn(`Moving to ${INSTALL_DIR} 🚙`)
exec('chmod', ['+x', extracted]) exec('chmod', ['+x', extracted])
exec('mv', [extracted, INSTALL_DIR + '/restic']) exec('mv', [extracted, INSTALL_DIR + '/restic'])
w.done(`\nFinished! restic is installed under: ${INSTALL_DIR}`.underline + ' 🎉') w.done(
}, `\nFinished! restic is installed under: ${INSTALL_DIR}`.underline + ' 🎉'
uninstall() { )
for (const bin of ['restic', 'autorestic']) },
try { uninstall() {
unlinkSync(INSTALL_DIR + '/' + bin) for (const bin of ['restic', 'autorestic'])
console.log(`Finished! ${bin} was uninstalled`) try {
} catch (e) { unlinkSync(INSTALL_DIR + '/' + bin)
console.log(`${bin} is already uninstalled`.red) 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... ⏳') async update() {
exec('restic', ['self-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... ⏳') if (json.tag_name != VERSION) {
const { data: json } = await axios({ const platformMap: { [key: string]: string } = {
method: 'get', darwin: 'macos',
url: 'https://api.github.com/repos/cupcakearmy/autorestic/releases/latest', }
responseType: 'json',
})
if (json.tag_name != VERSION) { const name = `autorestic_${platformMap[process.platform] ||
const platformMap: { [key: string]: string } = { process.platform}_${process.arch}`
'darwin': 'macos', const dl = json.assets.find((asset: any) => asset.name === name)
}
const name = `autorestic_${platformMap[process.platform] || process.platform}_${process.arch}` const to = INSTALL_DIR + '/autorestic'
const dl = json.assets.find((asset: any) => asset.name === name) w.replaceLn('Downloading binary... 🌎')
await downloadFile(dl.browser_download_url, to)
const to = INSTALL_DIR + '/autorestic' exec('chmod', ['+x', to])
w.replaceLn('Downloading binary... 🌎') }
await downloadFile(dl.browser_download_url, to)
exec('chmod', ['+x', to]) w.done('All up to date! 🚀')
} },
w.done('All up to date! 🚀')
},
} }
export const help = () => { export const help = () => {
console.log('\nAutorestic'.blue + ` - ${VERSION} - Easy Restic CLI Utility` console.log(
+ '\n' '\nAutorestic'.blue +
+ '\nOptions:'.yellow ` - ${VERSION} - Easy Restic CLI Utility` +
+ `\n -c, --config Specify config file. Default: ${CONFIG_FILE}` '\n' +
+ '\n' '\nOptions:'.yellow +
+ '\nCommands:'.yellow `\n -c, --config Specify config file. Default: .autorestic.yml` +
+ '\n check [-b, --backend] [-a, --all] Check backends' '\n' +
+ '\n backup [-l, --location] [-a, --all] Backup all or specified locations' '\nCommands:'.yellow +
+ '\n restore [-l, --location] [-- --target <out dir>] Check backends' '\n check [-b, --backend] [-a, --all] Check backends' +
+ '\n' '\n backup [-l, --location] [-a, --all] Backup all or specified locations' +
+ '\n exec [-b, --backend] [-a, --all] <command> -- [native options] Execute native restic command' '\n restore [-l, --location] [-- --target <out dir>] Restore all or specified locations' +
+ '\n' '\n' +
+ '\n install install restic' '\n exec [-b, --backend] [-a, --all] <command> -- [native options] Execute native restic command' +
+ '\n uninstall uninstall restic' '\n' +
+ '\n update update restic' '\n install install restic' +
+ '\n help Show help' '\n uninstall uninstall restic' +
+ '\n' '\n update update restic' +
+ '\nExamples: '.yellow + 'https://git.io/fjVbg' '\n help Show help' +
+ '\n', '\n' +
) '\nExamples: '.yellow +
'https://git.io/fjVbg' +
'\n'
)
} }
export const error = () => { export const error = () => {
help() help()
console.log(`Invalid Command:`.red.underline, `${process.argv.slice(2).join(' ')}`) console.log(
`Invalid Command:`.red.underline,
`${process.argv.slice(2).join(' ')}`
)
} }
export default handlers export default handlers