'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 const backupPath = path.join(apiConfig.mountPoint, apiConfig.prefix); const stat = safe.fs.statSync(backupPath); if (!stat) return callback(new BoxError(BoxError.BAD_FIELD, 'Directory does not exist or cannot be accessed: ' + safe.error.message), { field: 'prefix' }); if (!stat.isDirectory()) return callback(new BoxError(BoxError.BAD_FIELD, 'Backup location is not a directory', { field: 'prefix' })); if (!safe.fs.mkdirSync(path.join(backupPath, 'snapshot')) && safe.error.code !== 'EEXIST') { if (safe.error && safe.error.code === 'EACCES') return callback(new BoxError(BoxError.BAD_FIELD, `Access denied. Run "chown yellowtent:yellowtent ${backupPath}" on the server`, { field: 'prefix' })); return callback(new BoxError(BoxError.BAD_FIELD, safe.error.message, { field: 'prefix' })); } if (!safe.fs.writeFileSync(path.join(backupPath, 'cloudron-testfile'), 'testcontent')) { return callback(new BoxError(BoxError.BAD_FIELD, `Unable to create test file as 'yellowtent' user in ${backupPath}: ${safe.error.message}. Check dir/mount permissions`, { field: 'prefix' })); } if (!safe.fs.unlinkSync(path.join(backupPath, 'cloudron-testfile'))) { return callback(new BoxError(BoxError.BAD_FIELD, `Unable to remove test file as 'yellowtent' user in ${backupPath}: ${safe.error.message}. Check dir/mount permissions`, { field: 'prefix' })); } callback(null); } function removePrivateFields(apiConfig) { return apiConfig; } function injectPrivateFields(/* newConfig, currentConfig */) { }