autorestic/src/handlers.ts

245 lines
7.2 KiB
TypeScript
Raw Normal View History

2019-12-22 14:25:52 +01:00
import { chmodSync, renameSync, unlinkSync } from 'fs'
2019-06-21 12:27:39 +02:00
import { tmpdir } from 'os'
2019-06-20 23:09:47 +02:00
import { join, resolve } from 'path'
2019-12-22 14:25:52 +01:00
import axios from 'axios'
import { Writer } from 'clitastic'
2019-10-26 20:07:19 +02:00
import { config, INSTALL_DIR, VERSION } from './autorestic'
2020-05-17 11:13:02 +02:00
import { checkAndConfigureBackends, getEnvFromBackend, checkAndConfigureBackendsForLocations } from './backend'
2019-06-20 23:09:47 +02:00
import { backupAll } from './backup'
2020-05-17 11:13:02 +02:00
import { runCron } from './cron'
import { forgetAll } from './forget'
import showAll from './info'
2019-12-24 16:52:27 +01:00
import { restoreSingle } from './restore'
2019-12-05 00:23:49 +01:00
import { Backends, Flags, Locations } from './types'
2019-06-21 12:27:39 +02:00
import {
2019-12-03 23:37:55 +01:00
checkIfCommandIsAvailable,
checkIfResticIsAvailable,
downloadFile,
exec,
filterObjectByKey,
makeArrayIfIsNot,
2019-06-21 12:27:39 +02:00
} from './utils'
2019-06-20 23:09:47 +02:00
2019-12-03 23:37:55 +01:00
2019-10-26 20:07:19 +02:00
export type Handlers = {
2019-12-03 23:37:55 +01:00
[command: string]: (args: string[], flags: Flags) => void
2019-10-26 20:07:19 +02:00
}
2019-06-20 23:09:47 +02:00
const parseBackend = (flags: Flags): Backends => {
2019-12-03 23:37:55 +01:00
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 {
2019-12-05 00:23:49 +01:00
const backends = makeArrayIfIsNot<string>(flags.backend)
2019-12-03 23:37:55 +01:00
for (const backend of backends)
if (!config.backends[backend])
throw new Error('Invalid backend: '.red + backend)
return filterObjectByKey(config.backends, backends)
}
2019-06-20 23:09:47 +02:00
}
const parseLocations = (flags: Flags): Locations => {
2019-12-03 23:37:55 +01:00
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 {
2019-12-05 00:23:49 +01:00
const locations = makeArrayIfIsNot<string>(flags.location)
2019-12-03 23:37:55 +01:00
for (const location of locations)
if (!config.locations[location])
throw new Error('Invalid location: '.red + location)
return filterObjectByKey(config.locations, locations)
}
2019-06-20 23:09:47 +02:00
}
const handlers: Handlers = {
2019-12-03 23:37:55 +01:00
check(args, flags) {
checkIfResticIsAvailable()
const backends = parseBackend(flags)
checkAndConfigureBackends(backends)
},
backup(args, flags) {
checkIfResticIsAvailable()
const locations: Locations = parseLocations(flags)
2020-05-17 11:13:02 +02:00
checkAndConfigureBackendsForLocations(locations)
2019-12-03 23:37:55 +01:00
backupAll(locations)
console.log('\nFinished!'.underline + ' 🎉')
},
2020-05-17 11:13:02 +02:00
cron(args, flags) {
checkIfResticIsAvailable()
runCron()
},
2019-12-03 23:37:55 +01:00
restore(args, flags) {
checkIfResticIsAvailable()
2019-12-04 20:38:59 +01:00
2019-12-03 23:37:55 +01:00
const locations = parseLocations(flags)
2019-12-24 16:52:27 +01:00
const keys = Object.keys(locations)
if (keys.length < 1) throw new Error(`You need to specify the location to restore with --location`.red)
if (keys.length > 2) throw new Error(`Only one location is supported at a time when restoring`.red)
2019-12-03 23:37:55 +01:00
2019-12-24 16:52:27 +01:00
restoreSingle(keys[0], flags.from, flags.to)
2019-12-03 23:37:55 +01:00
},
forget(args, flags) {
checkIfResticIsAvailable()
const locations: Locations = parseLocations(flags)
2020-05-17 11:13:02 +02:00
checkAndConfigureBackendsForLocations(locations)
2019-12-04 20:38:59 +01:00
forgetAll(locations, flags)
2019-12-03 23:37:55 +01:00
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)
}
},
2019-12-24 16:52:27 +01:00
info() {
showAll()
},
2019-12-03 23:37:55 +01:00
async install() {
try {
checkIfResticIsAvailable()
console.log('Restic is already installed')
return
2019-12-22 14:25:52 +01:00
} catch {
2019-12-03 23:37:55 +01:00
}
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',
}
w.replaceLn('Downloading binary... 🌎')
2019-12-22 14:25:52 +01:00
const name = `${json.name.replace(' ', '_')}_${process.platform}_${archMap[process.arch]}.bz2`
2019-12-03 23:37:55 +01:00
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
await downloadFile(dl.browser_download_url, tmp)
w.replaceLn('Decompressing binary... 📦')
exec('bzip2', ['-dk', tmp])
unlinkSync(tmp)
w.replaceLn(`Moving to ${INSTALL_DIR} 🚙`)
2019-12-22 14:25:52 +01:00
chmodSync(extracted, 0o755)
renameSync(extracted, INSTALL_DIR + '/restic')
2019-12-03 23:37:55 +01:00
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',
})
if (json.tag_name != VERSION) {
const platformMap: { [key: string]: string } = {
darwin: 'macos',
}
2019-12-04 23:36:04 +01:00
const name = `autorestic_${platformMap[process.platform] || process.platform}_${process.arch}`
2019-12-03 23:37:55 +01:00
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)
2019-12-24 16:52:27 +01:00
chmodSync(to, 0o755)
2019-12-03 23:37:55 +01:00
}
w.done('All up to date! 🚀')
},
version() {
console.log('version'.grey, VERSION)
},
2019-06-20 23:09:47 +02:00
}
export const help = () => {
2019-12-03 23:37:55 +01:00
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 info Show all locations and backends' +
2019-12-03 23:37:55 +01:00
'\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' +
2019-12-04 20:50:32 +01:00
'\n restore [-l, --location] [--from backend] [--to <out dir>] Restore all or specified locations' +
2019-12-03 23:37:55 +01:00
'\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 +
2020-05-17 15:37:38 +02:00
'https://git.io/Jf0x6' +
2019-12-03 23:37:55 +01:00
'\n',
)
2019-06-20 23:09:47 +02:00
}
2019-12-04 20:53:06 +01:00
2019-06-20 23:09:47 +02:00
export const error = () => {
2019-12-03 23:37:55 +01:00
help()
console.log(
`Invalid Command:`.red.underline,
`${process.argv.slice(2).join(' ')}`,
)
2019-06-20 23:09:47 +02:00
}
2019-10-26 20:07:19 +02:00
export default handlers