'use strict'; exports = module.exports = { add, get, del, list }; const assert = require('assert'), BoxError = require('./boxerror.js'), collectd = require('./collectd.js'), debug = require('debug')('box:volumes'), ejs = require('ejs'), eventlog = require('./eventlog.js'), fs = require('fs'), path = require('path'), safe = require('safetydance'), services = require('./services.js'), uuid = require('uuid'), volumedb = require('./volumedb.js'); const COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd/volume.ejs', { encoding: 'utf8' }); const NOOP_CALLBACK = function (error) { if (error) debug(error); }; function validateName(name) { assert.strictEqual(typeof name, 'string'); if (!/^[-\w^&'@{}[\],$=!#().%+~ ]+$/.test(name)) return new BoxError(BoxError.BAD_FIELD, 'Invalid name'); return null; } function validateHostPath(hostPath) { assert.strictEqual(typeof hostPath, 'string'); if (path.normalize(hostPath) !== hostPath) return new BoxError(BoxError.BAD_FIELD, 'hostPath must contain a normalized path', { field: 'hostPath' }); if (!path.isAbsolute(hostPath)) return new BoxError(BoxError.BAD_FIELD, 'backupFolder must be an absolute path', { field: 'hostPath' }); if (hostPath === '/') return new BoxError(BoxError.BAD_FIELD, 'hostPath cannot be /', { field: 'hostPath' }); if (!hostPath.endsWith('/')) hostPath = hostPath + '/'; // ensure trailing slash for the prefix matching to work const allowedPaths = [ '/mnt/', '/media/', '/srv/', '/opt/' ]; if (!allowedPaths.some(p => hostPath.startsWith(p))) return new BoxError(BoxError.BAD_FIELD, 'hostPath must be under /mnt, /media, /opt or /srv', { field: 'hostPath' }); const stat = safe.fs.lstatSync(hostPath); if (!stat) return new BoxError(BoxError.BAD_FIELD, 'hostPath does not exist. Please create it on the server first', { field: 'hostPath' }); if (!stat.isDirectory()) return new BoxError(BoxError.BAD_FIELD, 'hostPath is not a directory', { field: 'hostPath' }); return null; } function add(name, hostPath, auditSource, callback) { assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof hostPath, 'string'); assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); let error = validateName(name); if (error) return callback(error); error = validateHostPath(hostPath); if (error) return callback(error); const id = uuid.v4(); volumedb.add(id, name, hostPath, function (error) { if (error) return callback(error); eventlog.add(eventlog.ACTION_VOLUME_ADD, auditSource, { id, name, hostPath }); services.rebuildService('sftp', NOOP_CALLBACK); const collectdConf = ejs.render(COLLECTD_CONFIG_EJS, { volumeId: id, hostPath }); collectd.addProfile(id, collectdConf, NOOP_CALLBACK); callback(null, id); }); } function get(id, callback) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof callback, 'function'); volumedb.get(id, function (error, result) { if (error) return callback(error); callback(null, result); }); } function list(callback) { assert.strictEqual(typeof callback, 'function'); volumedb.list(function (error, result) { if (error) return callback(error); return callback(null, result); }); } function del(volume, auditSource, callback) { assert.strictEqual(typeof volume, 'object'); assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); volumedb.del(volume.id, function (error) { if (error) return callback(error); eventlog.add(eventlog.ACTION_VOLUME_REMOVE, auditSource, { volume }); services.rebuildService('sftp', NOOP_CALLBACK); collectd.removeProfile(volume.id, NOOP_CALLBACK); return callback(null); }); }