diff --git a/scripts/installer.sh b/scripts/installer.sh index a98fc4681..93fc880e5 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -92,6 +92,9 @@ if [[ ${try} -eq 10 ]]; then exit 4 fi +echo "==> installer: ensure sshfs is installed" +apt install -y sshfs + echo "==> installer: downloading new addon images" images=$(node -e "var i = require('${box_src_tmp_dir}/src/infra_version.js'); console.log(i.baseImages.map(function (x) { return x.tag; }).join(' '), Object.keys(i.images).map(function (x) { return i.images[x].tag; }).join(' '));") diff --git a/src/backups.js b/src/backups.js index 581cc1bad..a4bceee18 100644 --- a/src/backups.js +++ b/src/backups.js @@ -88,6 +88,7 @@ function debugApp(app) { function api(provider) { switch (provider) { case 'cifs': return require('./storage/filesystem.js'); + case 'sshfs': return require('./storage/sshfs.js'); case 's3': return require('./storage/s3.js'); case 'gcs': return require('./storage/gcs.js'); case 'filesystem': return require('./storage/filesystem.js'); diff --git a/src/storage/sshfs.js b/src/storage/sshfs.js new file mode 100644 index 000000000..82494e519 --- /dev/null +++ b/src/storage/sshfs.js @@ -0,0 +1,119 @@ +'use strict'; + +exports = module.exports = { + upload: upload, + download: download, + + copy: copy, + + listDir: listDir, + + remove: remove, + removeDir: removeDir, + + testConfig: testConfig, + removePrivateFields: removePrivateFields, + injectPrivateFields: injectPrivateFields +}; + +var assert = require('assert'), + BoxError = require('../boxerror.js'), + debug = require('debug')('box:storage/sshfs'), + filesystem = require('./filesystem.js'), + mkdirp = require('mkdirp'), + path = require('path'), + safe = require('safetydance'), + shell = require('../shell.js'); + +// we mostly reuse the filesystem.js implementation but the config params are different +function translateConfig(apiConfig) { + assert.strictEqual(typeof apiConfig, 'object'); + + // skip chown + apiConfig._doNotChown = true; + + // do not preserver file attributes + apiConfig._doNotPreserveAttributes = true; + + // resolve the actual path + apiConfig.backupFolder = path.join(apiConfig.mountPoint, apiConfig.prefix); + + return apiConfig; +} + +// storage api +function upload(apiConfig, backupFilePath, sourceStream, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof backupFilePath, 'string'); + assert.strictEqual(typeof sourceStream, 'object'); + assert.strictEqual(typeof callback, 'function'); + + filesystem.upload(translateConfig(apiConfig), backupFilePath, sourceStream, callback); +} + +function download(apiConfig, sourceFilePath, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof sourceFilePath, 'string'); + assert.strictEqual(typeof callback, 'function'); + + filesystem.download(translateConfig(apiConfig), sourceFilePath, callback); +} + +function listDir(apiConfig, dir, batchSize, iteratorCallback, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof dir, 'string'); + assert.strictEqual(typeof batchSize, 'number'); + assert.strictEqual(typeof iteratorCallback, 'function'); + assert.strictEqual(typeof callback, 'function'); + + filesystem.listDir(translateConfig(apiConfig), dir, batchSize, iteratorCallback, callback); +} + +function copy(apiConfig, oldFilePath, newFilePath) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof oldFilePath, 'string'); + assert.strictEqual(typeof newFilePath, 'string'); + + return filesystem.copy(translateConfig(apiConfig), oldFilePath, newFilePath); +} + +function remove(apiConfig, filename, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof filename, 'string'); + assert.strictEqual(typeof callback, 'function'); + + filename.remove(translateConfig(apiConfig), filename, callback); +} + +function removeDir(apiConfig, pathPrefix) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof pathPrefix, 'string'); + + return filesystem.removeDir(translateConfig(apiConfig), pathPrefix); +} + +function testConfig(apiConfig, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof callback, 'function'); + + if (!apiConfig.mountPoint || typeof apiConfig.mountPoint !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'mountPoint must be non-empty string', { field: 'mountPoint' })); + + if ('noHardlinks' in apiConfig && typeof apiConfig.noHardlinks !== 'boolean') return callback(new BoxError(BoxError.BAD_FIELD, 'noHardlinks must be boolean', { field: 'noHardLinks' })); + if (typeof apiConfig.prefix !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'prefix must be a string', { field: 'prefix' })); + + // TODO check fstab entry + // TODO check mountpoint + // TODO check we can stat backup dir + // TODO check backup dir is dir + // TODO check we can create 'snapshot' folder + // TODO check we can write to backup dir (create and remove test file) + + callback(null); +} + +function removePrivateFields(apiConfig) { + return apiConfig; +} + +function injectPrivateFields(/* newConfig, currentConfig */) { +}