mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2025-09-06 10:30:39 +00:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
de27034b94 | ||
|
9dafe9d36a | ||
|
d47e7d0912 | ||
|
e47d6be854 | ||
|
993fe072e2 | ||
|
3d1e28e574 | ||
|
3c0ebdfb4a | ||
|
2653633c91 | ||
|
6a17444c4c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.idea
|
.idea
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
config.yml
|
config.yml
|
||||||
bin
|
bin
|
||||||
|
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
semi: false
|
||||||
|
singleQuote: true
|
||||||
|
trailingComma: 'es5'
|
@@ -4,12 +4,13 @@
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"build:watch": "tsc -w",
|
"build:watch": "tsc -w",
|
||||||
"dev": "tsnd --no-notify --respawn ./src/autorestic.ts",
|
"dev": "tsnd --no-notify --respawn ./src/autorestic.ts",
|
||||||
"bin": "npm run build && pkg lib/autorestic.js --out-path bin"
|
"bin": "npm run build && pkg lib/autorestic.js --targets latest-macos-x64,latest-linux-x64 --out-path bin"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/decompress": "^4.2.3",
|
"@types/decompress": "^4.2.3",
|
||||||
"@types/js-yaml": "^3.12.1",
|
"@types/js-yaml": "^3.12.1",
|
||||||
"@types/minimist": "^1.2.0",
|
"@types/minimist": "^1.2.0",
|
||||||
|
"@types/node": "^12.11.7",
|
||||||
"pkg": "^4.4.0",
|
"pkg": "^4.4.0",
|
||||||
"ts-node-dev": "^1.0.0-pre.40",
|
"ts-node-dev": "^1.0.0-pre.40",
|
||||||
"typescript": "^3.5.1"
|
"typescript": "^3.5.1"
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
import 'colors'
|
import 'colors'
|
||||||
import minimist from 'minimist'
|
import minimist from 'minimist'
|
||||||
import { homedir } from 'os'
|
|
||||||
import { resolve } from 'path'
|
|
||||||
|
|
||||||
import { init } from './config'
|
import { init } from './config'
|
||||||
import handlers, { error, help } from './handlers'
|
import handlers, { error, help } from './handlers'
|
||||||
import { Config } from './types'
|
import { Config } from './types'
|
||||||
|
|
||||||
|
|
||||||
process.on('uncaughtException', err => {
|
process.on('uncaughtException', err => {
|
||||||
console.log(err.message)
|
console.log(err.message)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
@@ -15,29 +12,34 @@ process.on('uncaughtException', err => {
|
|||||||
|
|
||||||
export const { _: commands, ...flags } = minimist(process.argv.slice(2), {
|
export const { _: commands, ...flags } = minimist(process.argv.slice(2), {
|
||||||
alias: {
|
alias: {
|
||||||
'c': 'config',
|
c: 'config',
|
||||||
'v': 'verbose',
|
v: 'version',
|
||||||
'h': 'help',
|
h: 'help',
|
||||||
'a': 'all',
|
a: 'all',
|
||||||
'l': 'location',
|
l: 'location',
|
||||||
'b': 'backend',
|
b: 'backend',
|
||||||
},
|
},
|
||||||
boolean: ['a'],
|
boolean: ['a'],
|
||||||
string: ['l', 'b'],
|
string: ['l', 'b'],
|
||||||
})
|
})
|
||||||
|
|
||||||
export const VERSION = '0.3'
|
export const VERSION = '0.5'
|
||||||
export const DEFAULT_CONFIG = '/.autorestic.yml'
|
|
||||||
export const INSTALL_DIR = '/usr/local/bin'
|
export const INSTALL_DIR = '/usr/local/bin'
|
||||||
export const CONFIG_FILE: string = resolve(flags.config || homedir() + DEFAULT_CONFIG)
|
|
||||||
export const VERBOSE = flags.verbose
|
export const VERBOSE = flags.verbose
|
||||||
|
|
||||||
export const config: Config = init()
|
if (flags.version) {
|
||||||
|
console.log('version'.grey, VERSION)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = init()
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
if (commands.length < 1) return help()
|
||||||
|
|
||||||
if (commands.length < 1)
|
|
||||||
help()
|
|
||||||
else {
|
|
||||||
const command: string = commands[0]
|
const command: string = commands[0]
|
||||||
const args: string[] = commands.slice(1)
|
const args: string[] = commands.slice(1)
|
||||||
;(handlers[command] || error)(args, flags)
|
;(handlers[command] || error)(args, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
@@ -2,11 +2,10 @@ 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':
|
||||||
@@ -24,7 +23,6 @@ export const getPathFromBackend = (backend: Backend): string => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getEnvFromBackend = (backend: Backend) => {
|
export const getEnvFromBackend = (backend: Backend) => {
|
||||||
const { type, path, key, ...rest } = backend
|
const { type, path, key, ...rest } = backend
|
||||||
return {
|
return {
|
||||||
@@ -34,7 +32,6 @@ export const getEnvFromBackend = (backend: Backend) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -49,8 +46,12 @@ export const checkAndConfigureBackend = (name: string, backend: Backend) => {
|
|||||||
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,34 +3,44 @@ 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 { 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 cmd = exec('restic', ['backup', from], { env: getEnvFromBackend(backend) })
|
const pathRelativeToConfigFile = resolve(dirname(CONFIG_FILE), from)
|
||||||
|
|
||||||
|
const cmd = exec('restic', ['backup', pathRelativeToConfigFile], {
|
||||||
|
env: getEnvFromBackend(backend),
|
||||||
|
})
|
||||||
|
|
||||||
if (VERBOSE) console.log(cmd.out, cmd.err)
|
if (VERBOSE) console.log(cmd.out, cmd.err)
|
||||||
writer.done(name + to.blue + ' : ' + 'Done ✓'.green)
|
writer.done(name + to.blue + ' : ' + 'Done ✓'.green)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const backupLocation = (name: string, backup: Location) => {
|
export const backupLocation = (name: string, backup: Location) => {
|
||||||
const display = name.yellow + ' ▶ '
|
const display = name.yellow + ' ▶ '
|
||||||
if (Array.isArray(backup.to)) {
|
if (Array.isArray(backup.to)) {
|
||||||
let first = true
|
let first = true
|
||||||
for (const t of backup.to) {
|
for (const t of backup.to) {
|
||||||
const nameOrBlankSpaces: string = first ? display : new Array(name.length + 3).fill(' ').join('')
|
const nameOrBlankSpaces: string = first
|
||||||
|
? display
|
||||||
|
: new Array(name.length + 3).fill(' ').join('')
|
||||||
backupSingle(nameOrBlankSpaces, backup.from, t)
|
backupSingle(nameOrBlankSpaces, backup.from, t)
|
||||||
if (first) first = false
|
if (first) first = false
|
||||||
}
|
}
|
||||||
} else
|
} else backupSingle(display, backup.from, backup.to)
|
||||||
backupSingle(display, backup.from, backup.to)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const backupAll = (backups?: Locations) => {
|
||||||
|
if (!backups) {
|
||||||
|
if (!config) throw ConfigError
|
||||||
|
backups = config.locations
|
||||||
|
}
|
||||||
|
|
||||||
export const backupAll = (backups: Locations = 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)
|
||||||
|
@@ -1,16 +1,21 @@
|
|||||||
import { readFileSync, writeFileSync } from 'fs'
|
import { readFileSync, writeFileSync, statSync } from 'fs'
|
||||||
|
import { resolve } from 'path'
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
import { CONFIG_FILE } from './autorestic'
|
import { flags } from './autorestic'
|
||||||
import { Backend, Config } from './types'
|
import { Backend, Config } from './types'
|
||||||
import { makeObjectKeysLowercase, rand } from './utils'
|
import { makeObjectKeysLowercase, rand } from './utils'
|
||||||
|
import { homedir } from 'os'
|
||||||
|
|
||||||
export const normalizeAndCheckBackends = (config: Config) => {
|
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)) {
|
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`)
|
)) {
|
||||||
|
if (!type || !path)
|
||||||
|
throw new Error(
|
||||||
|
`The backend "${name}" is missing some required attributes`
|
||||||
|
)
|
||||||
|
|
||||||
const tmp: any = {
|
const tmp: any = {
|
||||||
type,
|
type,
|
||||||
@@ -24,7 +29,6 @@ export const normalizeAndCheckBackends = (config: Config) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const normalizeAndCheckBackups = (config: Config) => {
|
export const normalizeAndCheckBackups = (config: Config) => {
|
||||||
config.locations = makeObjectKeysLowercase(config.locations)
|
config.locations = makeObjectKeysLowercase(config.locations)
|
||||||
const backends = Object.keys(config.backends)
|
const backends = Object.keys(config.backends)
|
||||||
@@ -34,20 +38,47 @@ export const normalizeAndCheckBackups = (config: Config) => {
|
|||||||
throw new Error(`Cannot find the backend "${backend}" for "${backup}"`)
|
throw new Error(`Cannot find the backend "${backend}" for "${backup}"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [name, { from, to, ...rest }] of Object.entries(config.locations)) {
|
for (const [name, { from, to, ...rest }] of Object.entries(
|
||||||
if (!from || !to) throw new Error(`The backup "${name}" is missing some required attributes`)
|
config.locations
|
||||||
|
)) {
|
||||||
|
if (!from || !to)
|
||||||
|
throw new Error(
|
||||||
|
`The backup "${name}" is missing some required attributes`
|
||||||
|
)
|
||||||
|
|
||||||
if (Array.isArray(to))
|
if (Array.isArray(to)) for (const t of to) checkDestination(t, name)
|
||||||
for (const t of to)
|
else checkDestination(to, name)
|
||||||
checkDestination(t, name)
|
|
||||||
else
|
|
||||||
checkDestination(to, name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findConfigFile = (): string => {
|
||||||
|
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) {}
|
||||||
|
}
|
||||||
|
throw new Error('No Config file found')
|
||||||
|
}
|
||||||
|
|
||||||
export const init = (): Config => {
|
export let CONFIG_FILE: string = ''
|
||||||
const raw: Config = makeObjectKeysLowercase(yaml.safeLoad(readFileSync(CONFIG_FILE).toString()))
|
|
||||||
|
export const init = (): Config | undefined => {
|
||||||
|
try {
|
||||||
|
CONFIG_FILE = findConfigFile()
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw: Config = makeObjectKeysLowercase(
|
||||||
|
yaml.safeLoad(readFileSync(CONFIG_FILE).toString())
|
||||||
|
)
|
||||||
|
|
||||||
normalizeAndCheckBackends(raw)
|
normalizeAndCheckBackends(raw)
|
||||||
normalizeAndCheckBackups(raw)
|
normalizeAndCheckBackups(raw)
|
||||||
|
115
src/handlers.ts
115
src/handlers.ts
@@ -4,7 +4,7 @@ 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'
|
||||||
@@ -15,18 +15,22 @@ import {
|
|||||||
exec,
|
exec,
|
||||||
filterObjectByKey,
|
filterObjectByKey,
|
||||||
singleToArray,
|
singleToArray,
|
||||||
|
ConfigError,
|
||||||
} 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 (!config) throw ConfigError
|
||||||
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)
|
if (flags.all) return config.backends
|
||||||
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)
|
||||||
@@ -37,10 +41,12 @@ 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('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) {
|
||||||
@@ -61,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)
|
||||||
|
|
||||||
@@ -68,22 +75,29 @@ const handlers: Handlers = {
|
|||||||
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(
|
||||||
|
filterObjectByKey(config.backends, Array.from(backends))
|
||||||
|
)
|
||||||
backupAll(locations)
|
backupAll(locations)
|
||||||
|
|
||||||
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)) {
|
||||||
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 🎉')
|
||||||
}
|
}
|
||||||
@@ -104,8 +118,7 @@ const handlers: Handlers = {
|
|||||||
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')
|
||||||
@@ -116,16 +129,19 @@ const handlers: Handlers = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
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}_${
|
||||||
|
archMap[process.arch]
|
||||||
|
}.bz2`
|
||||||
const dl = json.assets.find((asset: any) => asset.name === name)
|
const dl = json.assets.find((asset: any) => asset.name === name)
|
||||||
if (!dl) return console.log(
|
if (!dl)
|
||||||
|
return console.log(
|
||||||
'Cannot get the right binary.'.red,
|
'Cannot get the right binary.'.red,
|
||||||
'Please see https://bit.ly/2Y1Rzai',
|
'Please see https://bit.ly/2Y1Rzai'
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmp = join(tmpdir(), name)
|
const tmp = join(tmpdir(), name)
|
||||||
@@ -143,7 +159,9 @@ const handlers: Handlers = {
|
|||||||
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() {
|
uninstall() {
|
||||||
for (const bin of ['restic', 'autorestic'])
|
for (const bin of ['restic', 'autorestic'])
|
||||||
@@ -159,20 +177,21 @@ const handlers: Handlers = {
|
|||||||
const w = new Writer('Checking for latest restic version... ⏳')
|
const w = new Writer('Checking for latest restic version... ⏳')
|
||||||
exec('restic', ['self-update'])
|
exec('restic', ['self-update'])
|
||||||
|
|
||||||
|
|
||||||
w.replaceLn('Checking for latest autorestic version... ⏳')
|
w.replaceLn('Checking for latest autorestic version... ⏳')
|
||||||
const { data: json } = await axios({
|
const { data: json } = await axios({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: 'https://api.github.com/repos/cupcakearmy/autorestic/releases/latest',
|
url:
|
||||||
|
'https://api.github.com/repos/cupcakearmy/autorestic/releases/latest',
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (json.tag_name != VERSION) {
|
if (json.tag_name != VERSION) {
|
||||||
const platformMap: { [key: string]: string } = {
|
const platformMap: { [key: string]: string } = {
|
||||||
'darwin': 'macos',
|
darwin: 'macos',
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = `autorestic_${platformMap[process.platform] || process.platform}_${process.arch}`
|
const name = `autorestic_${platformMap[process.platform] ||
|
||||||
|
process.platform}_${process.arch}`
|
||||||
const dl = json.assets.find((asset: any) => asset.name === name)
|
const dl = json.assets.find((asset: any) => asset.name === name)
|
||||||
|
|
||||||
const to = INSTALL_DIR + '/autorestic'
|
const to = INSTALL_DIR + '/autorestic'
|
||||||
@@ -187,30 +206,36 @@ const handlers: Handlers = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
65
src/types.ts
65
src/types.ts
@@ -1,62 +1,69 @@
|
|||||||
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[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
src/utils.ts
36
src/utils.ts
@@ -3,8 +3,11 @@ 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, {
|
const cmd = spawnSync(command, args, {
|
||||||
...rest,
|
...rest,
|
||||||
env: {
|
env: {
|
||||||
@@ -19,9 +22,11 @@ export const exec = (command: string, args: string[], { env, ...rest }: SpawnSyn
|
|||||||
return { out, err }
|
return { out, err }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkIfResticIsAvailable = () => checkIfCommandIsAvailable(
|
export const checkIfResticIsAvailable = () =>
|
||||||
|
checkIfCommandIsAvailable(
|
||||||
'restic',
|
'restic',
|
||||||
'Restic is not installed'.red + ' https://restic.readthedocs.io/en/latest/020_installation.html#stable-releases',
|
'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) => {
|
||||||
@@ -31,22 +36,29 @@ export const checkIfCommandIsAvailable = (cmd: string, errorMsg?: string) => {
|
|||||||
|
|
||||||
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) =>
|
||||||
|
new Promise<void>(async res => {
|
||||||
const { data: file } = await axios({
|
const { data: file } = await axios({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: url,
|
url: url,
|
||||||
@@ -61,3 +73,5 @@ export const downloadFile = async (url: string, to: string) => new Promise<void>
|
|||||||
res()
|
res()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const ConfigError = new Error('Config file not found')
|
||||||
|
Reference in New Issue
Block a user