diff --git a/src/apps.js b/src/apps.js index 983aba8e5..03491042d 100644 --- a/src/apps.js +++ b/src/apps.js @@ -56,6 +56,7 @@ exports = module.exports = { backup, listBackups, updateBackup, + getBackupDownloadStream, getTask, getLogPaths, @@ -175,9 +176,11 @@ const appstore = require('./appstore.js'), shell = require('./shell.js'), spawn = require('child_process').spawn, split = require('split'), + storage = require('./storage.js'), superagent = require('superagent'), system = require('./system.js'), tasks = require('./tasks.js'), + tgz = require('./backupformat/tgz.js'), TransformStream = require('stream').Transform, users = require('./users.js'), util = require('util'), @@ -2617,6 +2620,25 @@ async function updateBackup(app, backupId, data) { await backups.update(backupId, data); } +async function getBackupDownloadStream(app, backupId) { + assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof backupId, 'string'); + + const backup = await backups.get(backupId); + if (!backup) throw new BoxError(BoxError.NOT_FOUND, 'Backup not found'); + if (backup.identifier !== app.id) throw new BoxError(BoxError.NOT_FOUND, 'Backup not found'); // some other app's backup + if (backup.format !== 'tgz') throw new BoxError(BoxError.BAD_STATE, 'only tgz backups can be downloaded'); + + const backupConfig = await settings.getBackupConfig(); + + return new Promise((resolve, reject) => { + storage.api(backupConfig.provider).download(backupConfig, tgz.getBackupFilePath(backupConfig, backup.remotePath), function (error, sourceStream) { + if (error) return reject(error); + resolve(sourceStream); + }); + }); +} + async function restoreInstalledApps(options, auditSource) { assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof auditSource, 'object'); diff --git a/src/routes/apps.js b/src/routes/apps.js index 3c9dd6626..ccd05e023 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -56,6 +56,7 @@ exports = module.exports = { downloadFile, updateBackup, + downloadBackup, getLimits, getGraphs, @@ -852,6 +853,17 @@ async function updateBackup(req, res, next) { next(new HttpSuccess(200, {})); } +async function downloadBackup(req, res, next) { + assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.params.backupId, 'string'); + + const [error, result] = await safe(apps.getBackupDownloadStream(req.app, req.params.backupId)); + if (error) return next(BoxError.toHttpError(error)); + + res.attachment(`${req.params.backupId}.tgz`); + result.pipe(res); +} + async function uploadFile(req, res, next) { assert.strictEqual(typeof req.app, 'object'); diff --git a/src/server.js b/src/server.js index 615996f43..b5296b411 100644 --- a/src/server.js +++ b/src/server.js @@ -242,6 +242,7 @@ function initializeExpressSync() { router.post('/api/v1/apps/:id/backup', json, token, routes.apps.load, authorizeOperator, routes.apps.backup); router.get ('/api/v1/apps/:id/backups', token, routes.apps.load, authorizeOperator, routes.apps.listBackups); router.post('/api/v1/apps/:id/backups/:backupId', json, token, routes.apps.load, authorizeOperator, routes.apps.updateBackup); + router.get ('/api/v1/apps/:id/backups/:backupId/download', token, routes.apps.load, authorizeOperator, routes.apps.downloadBackup); router.post('/api/v1/apps/:id/start', json, token, routes.apps.load, authorizeOperator, routes.apps.start); router.post('/api/v1/apps/:id/stop', json, token, routes.apps.load, authorizeOperator, routes.apps.stop); router.post('/api/v1/apps/:id/restart', json, token, routes.apps.load, authorizeOperator, routes.apps.restart);