2016-09-15 11:29:45 +02:00
'use strict' ;
exports = module . exports = {
2017-04-11 11:00:55 +02:00
backup : backup ,
restore : restore ,
2017-04-17 14:46:19 +02:00
copyBackup : copyBackup ,
2017-04-23 11:34:46 -07:00
removeBackups : removeBackups ,
2016-09-16 11:21:08 +02:00
2017-01-04 16:22:58 -08:00
backupDone : backupDone ,
2016-10-11 11:36:25 +02:00
testConfig : testConfig
2016-09-15 11:29:45 +02:00
} ;
2017-04-20 15:15:49 +02:00
var assert = require ( 'assert' ) ,
2016-10-10 16:10:51 +02:00
async = require ( 'async' ) ,
2016-10-10 13:21:45 +02:00
BackupsError = require ( '../backups.js' ) . BackupsError ,
2017-04-21 17:21:10 +02:00
config = require ( '../config.js' ) ,
2017-04-11 11:00:55 +02:00
debug = require ( 'debug' ) ( 'box:storage/filesystem' ) ,
2016-09-16 11:55:59 +02:00
fs = require ( 'fs' ) ,
2017-04-11 11:00:55 +02:00
mkdirp = require ( 'mkdirp' ) ,
once = require ( 'once' ) ,
2017-04-18 15:32:59 +02:00
path = require ( 'path' ) ,
2017-04-20 16:59:17 -07:00
safe = require ( 'safetydance' ) ,
2017-04-21 15:28:25 -07:00
targz = require ( './targz.js' ) ;
2016-09-16 11:55:59 +02:00
2016-09-16 14:10:34 +02:00
var FALLBACK _BACKUP _FOLDER = '/var/backups' ;
2017-04-20 16:40:35 +02:00
var FILE _TYPE = '.tar.gz.enc' ;
2017-04-21 17:21:10 +02:00
var BACKUP _USER = config . TEST ? process . env . USER : 'yellowtent' ;
2016-09-15 11:29:45 +02:00
2017-04-17 14:46:19 +02:00
// internal only
2017-04-18 14:39:48 +02:00
function getBackupFilePath ( apiConfig , backupId ) {
assert . strictEqual ( typeof apiConfig , 'object' ) ;
assert . strictEqual ( typeof backupId , 'string' ) ;
return path . join ( apiConfig . backupFolder || FALLBACK _BACKUP _FOLDER , backupId . endsWith ( FILE _TYPE ) ? backupId : backupId + FILE _TYPE ) ;
}
2017-04-18 15:32:59 +02:00
// storage api
2017-04-20 14:11:26 -07:00
function backup ( apiConfig , backupId , sourceDirectories , callback ) {
2016-09-15 11:29:45 +02:00
assert . strictEqual ( typeof apiConfig , 'object' ) ;
2017-04-11 11:00:55 +02:00
assert . strictEqual ( typeof backupId , 'string' ) ;
2017-04-20 14:11:26 -07:00
assert ( Array . isArray ( sourceDirectories ) ) ;
2016-09-15 11:29:45 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-04-11 11:00:55 +02:00
callback = once ( callback ) ;
2016-09-16 10:58:34 +02:00
2017-04-18 14:39:48 +02:00
var backupFilePath = getBackupFilePath ( apiConfig , backupId ) ;
2016-09-16 10:58:34 +02:00
2017-04-20 14:11:26 -07:00
debug ( '[%s] backup: %j -> %s' , backupId , sourceDirectories , backupFilePath ) ;
2016-09-15 11:29:45 +02:00
2017-04-20 16:59:17 -07:00
mkdirp ( path . dirname ( backupFilePath ) , function ( error ) {
2017-04-20 19:00:12 -07:00
if ( error ) return callback ( new BackupsError ( BackupsError . EXTERNAL _ERROR , error . message ) ) ;
2016-09-16 11:21:08 +02:00
2017-04-20 16:59:17 -07:00
var fileStream = fs . createWriteStream ( backupFilePath ) ;
2016-09-16 11:21:08 +02:00
2017-04-20 15:35:52 +02:00
fileStream . on ( 'error' , function ( error ) {
2017-04-23 22:09:05 -07:00
debug ( '[%s] backup: out stream error.' , backupId , error ) ;
2017-04-20 19:00:12 -07:00
callback ( new BackupsError ( BackupsError . EXTERNAL _ERROR , error . message ) ) ;
2017-04-20 15:35:52 +02:00
} ) ;
2017-04-11 11:00:55 +02:00
fileStream . on ( 'close' , function ( ) {
2017-04-20 16:59:17 -07:00
debug ( '[%s] backup: changing ownership.' , backupId ) ;
2017-04-21 17:21:10 +02:00
if ( ! safe . child _process . execSync ( 'chown -R ' + BACKUP _USER + ':' + BACKUP _USER + ' ' + path . dirname ( backupFilePath ) ) ) return callback ( new BackupsError ( BackupsError . INTERNAL _ERROR , safe . error . message ) ) ;
2017-04-20 16:59:17 -07:00
2017-04-11 11:00:55 +02:00
debug ( '[%s] backup: done.' , backupId ) ;
2017-04-20 16:59:17 -07:00
2017-04-17 14:46:19 +02:00
callback ( null ) ;
2017-04-11 11:00:55 +02:00
} ) ;
2017-04-21 15:28:25 -07:00
targz . create ( sourceDirectories , apiConfig . key || '' , fileStream , callback ) ;
2017-04-11 11:00:55 +02:00
} ) ;
2016-09-16 11:21:08 +02:00
}
2017-04-20 15:15:49 +02:00
function restore ( apiConfig , backupId , destination , callback ) {
2016-09-15 11:29:45 +02:00
assert . strictEqual ( typeof apiConfig , 'object' ) ;
2017-04-11 11:00:55 +02:00
assert . strictEqual ( typeof backupId , 'string' ) ;
2017-04-20 15:15:49 +02:00
assert . strictEqual ( typeof destination , 'string' ) ;
2016-09-15 11:29:45 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-04-21 15:28:25 -07:00
callback = once ( callback ) ;
2017-04-21 18:56:09 +02:00
var isOldFormat = backupId . endsWith ( '.tar.gz' ) ;
var sourceFilePath = isOldFormat ? path . join ( apiConfig . backupFolder || FALLBACK _BACKUP _FOLDER , backupId ) : getBackupFilePath ( apiConfig , backupId ) ;
2017-04-11 11:00:55 +02:00
2017-04-20 15:15:49 +02:00
debug ( '[%s] restore: %s -> %s' , backupId , sourceFilePath , destination ) ;
2017-04-11 11:00:55 +02:00
if ( ! fs . existsSync ( sourceFilePath ) ) return callback ( new BackupsError ( BackupsError . NOT _FOUND , 'backup file does not exist' ) ) ;
2017-04-21 15:28:25 -07:00
var fileStream = fs . createReadStream ( sourceFilePath ) ;
2017-04-14 17:46:25 +02:00
2017-04-21 15:28:25 -07:00
fileStream . on ( 'error' , function ( error ) {
2017-04-23 22:09:05 -07:00
debug ( 'restore: file stream error.' , error ) ;
2017-04-21 15:28:25 -07:00
callback ( new BackupsError ( BackupsError . EXTERNAL _ERROR , error . message ) ) ;
2017-04-17 14:46:19 +02:00
} ) ;
2017-04-21 15:28:25 -07:00
targz . extract ( fileStream , isOldFormat , destination , apiConfig . key || '' , callback ) ;
2017-04-17 14:46:19 +02:00
}
function copyBackup ( apiConfig , oldBackupId , newBackupId , callback ) {
assert . strictEqual ( typeof apiConfig , 'object' ) ;
assert . strictEqual ( typeof oldBackupId , 'string' ) ;
assert . strictEqual ( typeof newBackupId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
callback = once ( callback ) ;
2017-04-18 14:39:48 +02:00
var oldFilePath = getBackupFilePath ( apiConfig , oldBackupId ) ;
var newFilePath = getBackupFilePath ( apiConfig , newBackupId ) ;
2017-04-17 14:46:19 +02:00
2017-04-20 15:15:49 +02:00
debug ( 'copyBackup: %s -> %s' , oldFilePath , newFilePath ) ;
2017-04-20 16:59:17 -07:00
mkdirp ( path . dirname ( newFilePath ) , function ( error ) {
2017-04-20 19:00:12 -07:00
if ( error ) return callback ( new BackupsError ( BackupsError . EXTERNAL _ERROR , error . message ) ) ;
2017-04-17 14:46:19 +02:00
2017-04-20 15:41:25 +02:00
var readStream = fs . createReadStream ( oldFilePath ) ;
var writeStream = fs . createWriteStream ( newFilePath ) ;
readStream . on ( 'error' , function ( error ) {
2017-04-23 22:09:05 -07:00
debug ( 'copyBackup: read stream error.' , error ) ;
2017-04-20 19:00:12 -07:00
callback ( new BackupsError ( BackupsError . EXTERNAL _ERROR , error . message ) ) ;
2017-04-20 15:41:25 +02:00
} ) ;
2017-04-20 15:15:49 +02:00
2017-04-20 15:41:25 +02:00
writeStream . on ( 'error' , function ( error ) {
2017-04-23 22:09:05 -07:00
debug ( 'copyBackup: write stream error.' , error ) ;
2017-04-20 19:00:12 -07:00
callback ( new BackupsError ( BackupsError . EXTERNAL _ERROR , error . message ) ) ;
2017-04-20 15:41:25 +02:00
} ) ;
2017-04-20 15:15:49 +02:00
2017-04-20 16:59:17 -07:00
writeStream . on ( 'close' , function ( ) {
2017-04-21 17:21:10 +02:00
if ( ! safe . child _process . execSync ( 'chown -R ' + BACKUP _USER + ':' + BACKUP _USER + ' ' + path . dirname ( newFilePath ) ) ) return callback ( new BackupsError ( BackupsError . INTERNAL _ERROR , safe . error . message ) ) ;
2017-04-20 16:59:17 -07:00
callback ( ) ;
} ) ;
2017-04-20 15:41:25 +02:00
readStream . pipe ( writeStream ) ;
} ) ;
2016-09-15 11:29:45 +02:00
}
2017-04-23 11:34:46 -07:00
function removeBackups ( apiConfig , backupIds , callback ) {
2017-04-17 15:45:58 +02:00
assert . strictEqual ( typeof apiConfig , 'object' ) ;
2017-04-23 11:34:46 -07:00
assert ( Array . isArray ( backupIds ) ) ;
2017-04-17 15:45:58 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-04-23 22:40:45 -07:00
async . eachSeries ( backupIds , function ( id , iteratorCallback ) {
2017-04-18 14:39:48 +02:00
var filePath = getBackupFilePath ( apiConfig , id ) ;
2017-04-17 15:45:58 +02:00
2017-04-23 11:34:46 -07:00
if ( ! safe . fs . unlinkSync ( filePath ) ) {
debug ( 'removeBackups: Unable to remove %s : %s' , filePath , safe . error . message ) ;
}
safe . fs . rmdirSync ( path . dirname ( filePath ) ) ; // try to cleanup empty directories
2017-04-23 22:40:45 -07:00
iteratorCallback ( ) ;
2017-04-17 15:45:58 +02:00
} , callback ) ;
}
2016-10-11 11:36:25 +02:00
function testConfig ( apiConfig , callback ) {
assert . strictEqual ( typeof apiConfig , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-04-21 15:37:57 +02:00
if ( 'backupFolder' in apiConfig && typeof apiConfig . backupFolder !== 'string' ) return callback ( new BackupsError ( BackupsError . BAD _FIELD , 'backupFolder must be string' ) ) ;
2016-10-11 11:36:25 +02:00
2017-04-21 15:37:57 +02:00
// default value will be used
if ( ! apiConfig . backupFolder ) return callback ( ) ;
fs . stat ( apiConfig . backupFolder , function ( error , result ) {
if ( error ) {
debug ( 'testConfig: %s' , apiConfig . backupFolder , error ) ;
return callback ( new BackupsError ( BackupsError . BAD _FIELD , 'Directory does not exist or cannot be accessed' ) ) ;
}
if ( ! result . isDirectory ( ) ) return callback ( new BackupsError ( BackupsError . BAD _FIELD , 'Backup location is not a directory' ) ) ;
callback ( null ) ;
} ) ;
2016-10-11 11:36:25 +02:00
}
2017-01-04 16:22:58 -08:00
2017-04-21 10:31:43 +02:00
function backupDone ( backupId , appBackupIds , callback ) {
assert . strictEqual ( typeof backupId , 'string' ) ;
assert ( Array . isArray ( appBackupIds ) ) ;
2017-01-04 16:22:58 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
callback ( ) ;
}