diff --git a/src/backups.js b/src/backups.js index 902804f75..509275f41 100644 --- a/src/backups.js +++ b/src/backups.js @@ -35,13 +35,13 @@ var addons = require('./addons.js'), debug = require('debug')('box:backups'), eventlog = require('./eventlog.js'), filesystem = require('./storage/filesystem.js'), - fs = require('fs'), locker = require('./locker.js'), mailer = require('./mailer.js'), path = require('path'), paths = require('./paths.js'), progress = require('./progress.js'), s3 = require('./storage/s3.js'), + safe = require('safetydance'), shell = require('./shell.js'), settings = require('./settings.js'), SettingsError = require('./settings.js').SettingsError, @@ -166,6 +166,23 @@ function copyLastBackup(app, manifest, prefix, callback) { }); } +function runBackupTask(backupId, appId, callback) { + assert.strictEqual(typeof backupId, 'string'); + assert(appId === null || typeof backupId === 'string'); + assert.strictEqual(typeof callback, 'function'); + + shell.sudo('backup' + (appId ? 'App' : 'Box'), [ NODE_CMD, BACKUPTASK_CMD, backupId ].concat(appId ? [ appId ] : [ ]), function (error) { + if (error && (error.code === null /* signal */ || (error.code !== 0 && error.code !== 50))) { // backuptask crashed + return callback(new BackupsError(BackupsError.INTERNAL_ERROR, 'backuptask crashed')); + } else if (error && error.code === 50) { // exited with error + var result = safe.fs.readFileSync(paths.BACKUP_RESULT_FILE, 'utf8') || safe.error.message; + return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, result)); + } + + callback(); + }); +} + function backupBoxWithAppBackupIds(appBackupIds, prefix, callback) { assert(Array.isArray(appBackupIds)); assert.strictEqual(typeof prefix, 'string'); @@ -180,8 +197,8 @@ function backupBoxWithAppBackupIds(appBackupIds, prefix, callback) { shell.exec('backupBox', '/bin/bash', mysqlDumpArgs, function (error) { if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error)); - shell.sudo('backupBox', [ NODE_CMD, BACKUPTASK_CMD, backupId ], function (error) { - if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error)); + runBackupTask(backupId, null /* appId */, function (error) { + if (error) return callback(error); debug('backupBoxWithAppBackupIds: success'); @@ -220,19 +237,23 @@ function createNewAppBackup(app, manifest, prefix, callback) { var restoreConfig = apps.getAppConfig(app); restoreConfig.manifest = manifest; - async.series([ - fs.writeFile.bind(null, path.join(paths.APPS_DATA_DIR, app.id + '/config.json'), JSON.stringify(restoreConfig)), - addons.backupAddons.bind(null, app, manifest.addons), - shell.sudo.bind(null, 'backupApp', [ NODE_CMD, BACKUPTASK_CMD, backupId, app.id ]) - ], function (error) { - if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error)); + if (!safe.fs.writeFileSync(path.join(paths.APPS_DATA_DIR, app.id + '/config.json'), JSON.stringify(restoreConfig))) { + return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, 'Error creating config.json: ' + safe.error.message)); + } - debugApp(app, 'createNewAppBackup: %s done', backupId); + addons.backupAddons(app, manifest.addons, function (error) { + if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error.message)); - backupdb.add({ id: backupId, version: manifest.version, type: backupdb.BACKUP_TYPE_APP, dependsOn: [ ], restoreConfig: restoreConfig }, function (error) { - if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error)); + runBackupTask(backupId, app.id, function (error) { + if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error.message)); - callback(null, backupId); + debugApp(app, 'createNewAppBackup: %s done', backupId); + + backupdb.add({ id: backupId, version: manifest.version, type: backupdb.BACKUP_TYPE_APP, dependsOn: [ ], restoreConfig: restoreConfig }, function (error) { + if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error)); + + callback(null, backupId); + }); }); }); } diff --git a/src/backuptask.js b/src/backuptask.js index 4f18f52f0..a27dc2278 100755 --- a/src/backuptask.js +++ b/src/backuptask.js @@ -10,14 +10,15 @@ require('debug').formatArgs = function formatArgs(args) { }; var assert = require('assert'), + BackupsError = require('./backups.js').BackupsError, + caas = require('./storage/caas.js'), database = require('./database.js'), debug = require('debug')('box:backuptask'), + filesystem = require('./storage/filesystem.js'), path = require('path'), paths = require('./paths.js'), - filesystem = require('./storage/filesystem.js'), - caas = require('./storage/caas.js'), s3 = require('./storage/s3.js'), - BackupsError = require('./backups.js').BackupsError, + safe = require('safetydance'), settings = require('./settings.js'); function api(provider) { @@ -90,9 +91,11 @@ initialize(function (error) { if (error) throw error; function resultHandler(error) { - if (error) debug('Backuptask completed with error', error); + if (error) debug('completed with error', error); - debug('Backuptask completed'); + debug('completed'); + + safe.fs.writeFileSync(paths.BACKUP_RESULT_FILE, error ? error.message : ''); // https://nodejs.org/api/process.html are exit codes used by node. apps.js uses the value below // to check apptask crashes diff --git a/src/paths.js b/src/paths.js index a7050be93..63641a48d 100644 --- a/src/paths.js +++ b/src/paths.js @@ -7,6 +7,7 @@ var config = require('./config.js'), exports = module.exports = { CLOUDRON_DEFAULT_AVATAR_FILE: path.join(__dirname + '/../assets/avatar.png'), INFRA_VERSION_FILE: path.join(config.baseDir(), 'platformdata/INFRA_VERSION'), + BACKUP_RESULT_FILE: path.join(config.baseDir(), 'platformdata/backupresult'), OLD_DATA_DIR: path.join(config.baseDir(), 'data'), PLATFORM_DATA_DIR: path.join(config.baseDir(), 'platformdata'), diff --git a/src/shell.js b/src/shell.js index c9d1e4816..033507349 100644 --- a/src/shell.js +++ b/src/shell.js @@ -50,8 +50,12 @@ function exec(tag, file, args, callback) { cp.on('exit', function (code, signal) { if (code || signal) debug(tag + ' code: %s, signal: %s', code, signal); + if (code === 0) return callback(); - callback(code === 0 ? null : new Error(util.format(tag + ' exited with error %s signal %s', code, signal))); + var e = new Error(util.format(tag + ' exited with error %s signal %s', code, signal)); + e.code = code; + e.signal = signal; + callback(e); }); cp.on('error', function (error) { @@ -70,6 +74,7 @@ function sudo(tag, args, callback) { // -S makes sudo read stdin for password var cp = exec(tag, SUDO, [ '-S' ].concat(args), callback); cp.stdin.end(); + return cp; } function sudoSync(tag, cmd, callback) {