const Command = require("../structures/Command") const { CommandWords } = require("../constants") const fs = require("fs") const { join } = require("path") const { getAllDbs } = require("../core/db") const tar = require("tar") const { progressBar } = require("../utils") const { TEMP_DIR, COUCH_DIR, MINIO_DIR, getConfig, replication, getPouches, } = require("./utils") const { exportObjects, importObjects } = require("./objectStore") async function exportBackup(opts) { const envFile = opts.env || undefined let filename = opts["export"] || opts if (typeof filename !== "string") { filename = `backup-${new Date().toISOString()}.tar.gz` } const config = await getConfig(envFile) const dbList = await getAllDbs(config["COUCH_DB_URL"]) 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) { const envFile = opts.env || undefined const filename = opts["import"] || opts 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() console.log("Import complete") fs.rmSync(TEMP_DIR, { recursive: true }) } async function pickOne(opts) { if (opts["import"]) { return importBackup(opts) } else if (opts["export"]) { return exportBackup(opts) } } const command = new Command(`${CommandWords.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 ) exports.command = command