132 lines
3.6 KiB
TypeScript
132 lines
3.6 KiB
TypeScript
import { Command } from "../structures/Command"
|
|
import { CommandWord } from "../constants"
|
|
import fs from "fs"
|
|
import { join } from "path"
|
|
import { getAllDbs } from "../core/db"
|
|
import { progressBar, httpCall } from "../utils"
|
|
import {
|
|
TEMP_DIR,
|
|
COUCH_DIR,
|
|
MINIO_DIR,
|
|
getConfig,
|
|
replication,
|
|
getPouches,
|
|
} from "./utils"
|
|
import { exportObjects, importObjects } from "./objectStore"
|
|
|
|
const tar = require("tar")
|
|
|
|
type BackupOpts = { env?: string; import?: string; export?: string }
|
|
|
|
async function exportBackup(opts: BackupOpts) {
|
|
const envFile = opts.env || undefined
|
|
let filename = opts["export"] || (opts as string)
|
|
if (typeof filename !== "string") {
|
|
filename = `backup-${new Date().toISOString()}.tar.gz`
|
|
}
|
|
const config = await getConfig(envFile)
|
|
const dbList = (await getAllDbs(config["COUCH_DB_URL"])) as string[]
|
|
const { Remote, Local } = getPouches(config)
|
|
if (fs.existsSync(TEMP_DIR)) {
|
|
fs.rmSync(TEMP_DIR, { recursive: true })
|
|
}
|
|
const couchDir = join(TEMP_DIR, COUCH_DIR)
|
|
fs.mkdirSync(TEMP_DIR)
|
|
fs.mkdirSync(couchDir)
|
|
console.log("CouchDB Export")
|
|
const bar = progressBar(dbList.length)
|
|
let count = 0
|
|
for (let db of dbList) {
|
|
bar.update(++count)
|
|
const remote = new Remote(db)
|
|
const local = new Local(join(TEMP_DIR, COUCH_DIR, db))
|
|
await replication(remote, local)
|
|
}
|
|
bar.stop()
|
|
console.log("S3 Export")
|
|
await exportObjects()
|
|
tar.create(
|
|
{
|
|
sync: true,
|
|
gzip: true,
|
|
file: filename,
|
|
cwd: join(TEMP_DIR),
|
|
},
|
|
[COUCH_DIR, MINIO_DIR]
|
|
)
|
|
fs.rmSync(TEMP_DIR, { recursive: true })
|
|
console.log(`Generated export file - ${filename}`)
|
|
}
|
|
|
|
async function importBackup(opts: BackupOpts) {
|
|
const envFile = opts.env || undefined
|
|
const filename = opts["import"] || (opts as string)
|
|
const config = await getConfig(envFile)
|
|
if (!filename || !fs.existsSync(filename)) {
|
|
console.error("Cannot import without specifying a valid file to import")
|
|
process.exit(-1)
|
|
}
|
|
if (fs.existsSync(TEMP_DIR)) {
|
|
fs.rmSync(TEMP_DIR, { recursive: true })
|
|
}
|
|
fs.mkdirSync(TEMP_DIR)
|
|
tar.extract({
|
|
sync: true,
|
|
cwd: join(TEMP_DIR),
|
|
file: filename,
|
|
})
|
|
const { Remote, Local } = getPouches(config)
|
|
const dbList = fs.readdirSync(join(TEMP_DIR, COUCH_DIR))
|
|
console.log("CouchDB Import")
|
|
const bar = progressBar(dbList.length)
|
|
let count = 0
|
|
for (let db of dbList) {
|
|
bar.update(++count)
|
|
const remote = new Remote(db)
|
|
const local = new Local(join(TEMP_DIR, COUCH_DIR, db))
|
|
await replication(local, remote)
|
|
}
|
|
bar.stop()
|
|
console.log("MinIO Import")
|
|
await importObjects()
|
|
// finish by letting the system know that a restore has occurred
|
|
try {
|
|
await httpCall(
|
|
`http://localhost:${config.MAIN_PORT}/api/system/restored`,
|
|
"POST"
|
|
)
|
|
} catch (err) {
|
|
// ignore error - it will be an older system
|
|
}
|
|
console.log("Import complete")
|
|
fs.rmSync(TEMP_DIR, { recursive: true })
|
|
}
|
|
|
|
async function pickOne(opts: BackupOpts) {
|
|
if (opts["import"]) {
|
|
return importBackup(opts)
|
|
} else if (opts["export"]) {
|
|
return exportBackup(opts)
|
|
}
|
|
}
|
|
|
|
export default new Command(`${CommandWord.BACKUPS}`)
|
|
.addHelp(
|
|
"Allows building backups of Budibase, as well as importing a backup to a new instance."
|
|
)
|
|
.addSubOption(
|
|
"--export [filename]",
|
|
"Export a backup from an existing Budibase installation.",
|
|
exportBackup
|
|
)
|
|
.addSubOption(
|
|
"--import [filename]",
|
|
"Import a backup to a new Budibase installation.",
|
|
importBackup
|
|
)
|
|
.addSubOption(
|
|
"--env [envFile]",
|
|
"Provide an environment variable file to configure the CLI.",
|
|
pickOne
|
|
)
|