UNPKG

walter

Version:

Backup utilities for postgresql

211 lines (179 loc) 6.19 kB
var env = process.env; var fs = require('fs'); var knox = require('knox'); var path = require('path'); var es = require('event-stream'); var buffers = require('buffers'); var optimist = require('optimist'); var spawn = require('child_process').spawn; var winston = require('winston'); var argv = optimist .usage('Push a wal file to s3.\nUsage: walter wal-push [options]') .describe('prefix', 'Prefix used to store files on s3 (env: WALTER_PREFIX)') .describe('gpg-id', 'Pass a GPG ID to encrypt your backups (env: WALTER_GPG_ID)') .describe('aws-key', 'Your AWS Key (env: WALTER_AWS_KEY)') .describe('aws-secret', 'Your AWS Secret (env: WALTER_AWS_SECRET)') .describe('aws-region', 'AWS region for your bucket (env: WALTER_AWS_REGION)') .describe('s3-bucket', 'S3 Bucket Name (env: WALTER_S3_BUCKET)') .describe('log-file-path', 'Winston file transport path (env: WALTER_LOG_FILE_PATH)') .describe('log-file-size', 'Winston file transport size in MB (env: WALTER_LOG_FILE_SIZE)') .describe('log-file-limit', 'Winston file transport rotate limit (env: WALTER_LOG_FILE_LIMIT)') .describe('labs-api-key', 'Walter Labs API Key (env: WALTER_LABS_API_KEY)') .describe('log-http-host', 'Winston HTTP transport host (env: WALTER_LOG_HTTP_HOST)') .describe('log-http-port', 'Winston HTTP transport port (env: WALTER_LOG_HTTP_PORT)') .describe('log-http-path', 'Winston HTTP transport path (env: WALTER_LOG_HTTP_PATH)') .describe('log-http-ssl', 'Winston HTTP transport ssl (env: WALTER_LOG_HTTP_SSL)') .describe('tar-bin', 'Path to tar binary') .describe('gpg-bin', 'Path to gpg binary') .describe('lzop-bin', 'Path to lzop binary') .describe('wal-file', 'Path to wal file') .default('prefix', env.WALTER_PREFIX) .default('gpg-id', env.WALTER_GPG_ID) .default('aws-key', env.WALTER_AWS_KEY) .default('aws-secret', env.WALTER_AWS_SECRET) .default('s3-bucket', env.WALTER_S3_BUCKET) .default('log-file-path', env.WALTER_LOG_FILE_PATH) .default('log-file-size', env.WALTER_LOG_FILE_SIZE) .default('log-file-limit', env.WALTER_LOG_FILE_LIMIT) .default('labs-api-key', env.WALTER_LABS_API_KEY) .default('log-http-host', env.WALTER_LOG_HTTP_HOST) .default('log-http-port', env.WALTER_LOG_HTTP_PORT) .default('log-http-path', env.WALTER_LOG_HTTP_PATH) .default('log-http-ssl', env.WALTER_LOG_HTTP_SSL) .default('tar-bin', 'tar') .default('gpg-bin', 'gpg') .default('lzop-bin', 'lzop') .demand('prefix') .demand('aws-key') .demand('aws-secret') .demand('s3-bucket') .demand('wal-file') .wrap(120) .argv; winston.remove(winston.transports.Console); if(argv['labs-api-key']) { argv['log-http-host'] = 'api.walter-labs.com'; argv['log-http-port'] = 443; argv['log-http-path'] = '/servers/' + argv['labs-api-key']; argv['log-http-ssl'] = true; } if(argv['log-http-host']) { var logHttpOpts = { host: argv['log-http-host'] }; if(argv['log-http-port']) logHttpOpts.port = argv['log-http-port']; if(argv['log-http-path']) logHttpOpts.path = argv['log-http-path']; if(argv['log-http-ssl'] === 'true' || argv['log-http-ssl'] === true) logHttpOpts.ssl = true; winston.add(winston.transports.Http, logHttpOpts); } if(argv['log-file-path']) { var logFileOpts = { filename: argv['log-file-path'] }; if(argv['log-file-size']) logFileOpts.maxsize = (argv['log-file-size']*1024*1024); if(argv['log-file-limit']) logFileOpts.maxFiles = argv['log-file-limit']; winston.add(winston.transports.File, logFileOpts); } var raw = 0; var compressed = 0; var started = new Date(); var filename = path.basename(argv['wal-file']); winston.info('wal-push: %s: %s, started.', argv['prefix'], filename, { type: 'wal', action: 'started', prefix: argv['prefix'], filename: filename, started: started }); module.exports = function() { var bufs = buffers(); var gpg = spawnGpg(); var lzop = spawnLzop(); var reader = fs.createReadStream(argv['wal-file']); var raw_counter = es.through(function(data) { raw += data.length; this.emit('data', data); }, function() { this.emit('end'); }); var compressed_counter = es.through(function(data) { compressed += data.length; this.emit('data', data); }, function() { this.emit('end'); }); var bufferer = es.through(function(data) { bufs.push(data); }, function() { this.emit('end'); }); var pipeline = gpg ? [reader, raw_counter, lzop, compressed_counter, gpg, bufferer] : [reader, raw_counter, lzop, compressed_counter, bufferer]; var pipe = es.pipeline.apply(null, pipeline); pipe.on('end', onEnd); function onEnd() { putWal(bufs.toBuffer()); } }; function putWal(buffer) { var s3 = createS3Client(); var wal_path = argv['prefix'] + '/wal/' + filename; s3.putBuffer(buffer, wal_path, { 'Content-Length': buffer.length }, onComplete); } function onComplete(err, response) { if(err || response.statusCode < 200 || response.statusCode > 299) { if(!err) err = new Error('Failed upload to S3'); return handleError(err); } winston.info('wal-push: %s: %s, completed.', argv['prefix'], filename, { type: 'wal', action: 'completed', prefix: argv['prefix'], filename: filename, size: raw, compressed: compressed, started: started, completed: new Date() }, function() { process.exit(); }); } function createS3Client() { var options = { key: argv['aws-key'], secret: argv['aws-secret'], bucket: argv['s3-bucket'] }; if(argv['aws-region']) options.region = argv['aws-region']; return new knox.createClient(options); } function spawnLzop() { return es.child(spawn(argv['lzop-bin'], ['-c', '-'])); } function spawnGpg() { if(!argv['gpg-id']) return false; var args = ['-e', '-z', '0', '-r', argv['gpg-id'], '-']; return es.child(spawn(argv['gpg-bin'], args)); } function handleError(err) { winston.error('wal-push: %s: %s, error occured.', argv['prefix'], filename, { prefix: argv['prefix'], filename: filename, error: err.message, started: started, errored: new Date() }, errorExit); } function errorExit() { process.exit(1); }