secure-scuttlebot classic

Merge branch 'plugins'

+7 -497
+3 -2
bin.js
··· 46 46 .use(require('./plugins/onion')) 47 47 .use(require('./plugins/unix-socket')) 48 48 .use(require('./plugins/no-auth')) 49 - .use(require('./plugins/plugins')) 49 + // .use(require('./plugins/plugins')) 50 + .use(require('ssb-plugins')) 50 51 .use(require('./plugins/master')) 51 52 .use(require('ssb-gossip')) 52 53 .use(require('ssb-replicate')) ··· 60 61 .use(require('ssb-ebt')) 61 62 .use(require('ssb-ooo')) 62 63 // add third-party plugins 63 - require('./plugins/plugins').loadUserPlugins(createSsbServer, config) 64 + require('ssb-plugins').loadUserPlugins(createSsbServer, config) 64 65 65 66 if (argv[1] != '--disable-ssb-links') { 66 67 if (!createSsbServer.plugins.find(p => p.name == 'links2')) {
+1
package.json
··· 57 57 "ssb-keys": "^7.1.1", 58 58 "ssb-links": "^3.0.2", 59 59 "ssb-ooo": "^1.2.2", 60 + "ssb-plugins": "0.0.0", 60 61 "ssb-query": "^2.1.0", 61 62 "ssb-ref": "^2.13.9", 62 63 "ssb-replicate": "^1.2.1",
-229
plugins/plugins.js
··· 1 - var assert = require('assert') 2 - var path = require('path') 3 - var fs = require('fs') 4 - var pull = require('pull-stream') 5 - var cat = require('pull-cat') 6 - var many = require('pull-many') 7 - var pushable = require('pull-pushable') 8 - var toPull = require('stream-to-pull-stream') 9 - var spawn = require('cross-spawn') 10 - var mkdirp = require('mkdirp') 11 - var osenv = require('osenv') 12 - var rimraf = require('rimraf') 13 - var mv = require('mv') 14 - var mdm = require('mdmanifest') 15 - var explain = require('explain-error') 16 - var valid = require('../lib/validators') 17 - 18 - module.exports = { 19 - name: 'plugins', 20 - version: '1.0.0', 21 - manifest: mdm.manifest(fs.readFileSync(path.join(__dirname, 'plugins.md'), 'utf8')), 22 - permissions: { 23 - master: {allow: ['install', 'uninstall', 'enable', 'disable']} 24 - }, 25 - init: function (server, config) { 26 - var installPath = config.path 27 - config.plugins = config.plugins || {} 28 - mkdirp.sync(path.join(installPath, 'node_modules')) 29 - 30 - // helper to enable/disable plugins 31 - function configPluginEnabled (b) { 32 - return function (pluginName, cb) { 33 - checkInstalled(pluginName, function (err) { 34 - if (err) return cb(err) 35 - 36 - config.plugins[pluginName] = b 37 - writePluginConfig(pluginName, b) 38 - if (b) 39 - cb(null, '\''+pluginName+'\' has been enabled. Restart ssb-server to use the plugin.') 40 - else 41 - cb(null, '\''+pluginName+'\' has been disabled. Restart ssb-server to stop using the plugin.') 42 - }) 43 - } 44 - } 45 - 46 - // helper to check if a plugin is installed 47 - function checkInstalled (pluginName, cb) { 48 - if (!pluginName || typeof pluginName !== 'string') 49 - return cb(new Error('plugin name is required')) 50 - var modulePath = path.join(installPath, 'node_modules', pluginName) 51 - fs.stat(modulePath, function (err) { 52 - if (err) 53 - cb(new Error('Plugin "'+pluginName+'" is not installed.')) 54 - else 55 - cb() 56 - }) 57 - } 58 - 59 - // write the plugin config to ~/.ssb/config 60 - function writePluginConfig (pluginName, value) { 61 - var cfgPath = path.join(config.path, 'config') 62 - // load ~/.ssb/config 63 - let existingConfig 64 - fs.readFile(cfgPath, 'utf-8', (err, data) => { 65 - if (err) { 66 - if (err.code === 'ENOENT') { 67 - // only catch "file not found" 68 - existingConfig = {} 69 - } else { 70 - throw err 71 - } 72 - } else { 73 - existingConfig = JSON.parse(data) 74 - } 75 - 76 - 77 - // update the plugins config 78 - existingConfig.plugins = existingConfig.plugins || {} 79 - existingConfig.plugins[pluginName] = value 80 - 81 - // write to disc 82 - fs.writeFileSync(cfgPath, JSON.stringify(existingConfig, null, 2), 'utf-8') 83 - }) 84 - 85 - } 86 - 87 - return { 88 - install: valid.source(function (pluginName, opts) { 89 - var p = pushable() 90 - var dryRun = opts && opts['dry-run'] 91 - var from = opts && opts.from 92 - 93 - if (!pluginName || typeof pluginName !== 'string') 94 - return pull.error(new Error('plugin name is required')) 95 - 96 - // pull out the version, if given 97 - if (pluginName.indexOf('@') !== -1) { 98 - var pluginNameSplitted = pluginName.split('@') 99 - pluginName = pluginNameSplitted[0] 100 - var version = pluginNameSplitted[1] 101 - 102 - if (version && !from) 103 - from = pluginName + '@' + version 104 - } 105 - 106 - if (!validatePluginName(pluginName)) 107 - return pull.error(new Error('invalid plugin name: "'+pluginName+'"')) 108 - 109 - // create a tmp directory to install into 110 - var tmpInstallPath = path.join(osenv.tmpdir(), pluginName) 111 - rimraf.sync(tmpInstallPath); mkdirp.sync(tmpInstallPath) 112 - 113 - // build args 114 - // --global-style: dont dedup at the top level, gives proper isolation between each plugin 115 - // --loglevel error: dont output warnings, because npm just whines about the lack of a package.json in ~/.ssb 116 - var args = ['install', from||pluginName, '--global-style', '--loglevel', 'error'] 117 - if (dryRun) 118 - args.push('--dry-run') 119 - 120 - // exec npm 121 - var child = spawn('npm', args, { cwd: tmpInstallPath }) 122 - .on('close', function (code) { 123 - if (code == 0 && !dryRun) { 124 - var tmpInstallNMPath = path.join(tmpInstallPath, 'node_modules') 125 - var finalInstallNMPath = path.join(installPath, 'node_modules') 126 - 127 - // delete plugin, if it's already there 128 - rimraf.sync(path.join(finalInstallNMPath, pluginName)) 129 - 130 - // move the plugin from the tmpdir into our install path 131 - // ...using our given plugin name 132 - var dirs = fs.readdirSync(tmpInstallNMPath) 133 - .filter(function (name) { return name.charAt(0) !== '.' }) // filter out dot dirs, like '.bin' 134 - mv( 135 - path.join(tmpInstallNMPath, dirs[0]), 136 - path.join(finalInstallNMPath, pluginName), 137 - function (err) { 138 - if (err) 139 - return p.end(explain(err, '"'+pluginName+'" failed to install. See log output above.')) 140 - 141 - // enable the plugin 142 - // - use basename(), because plugins can be installed from the FS, in which case pluginName is a path 143 - var name = path.basename(pluginName) 144 - config.plugins[name] = true 145 - writePluginConfig(name, true) 146 - p.push(Buffer.from('"'+pluginName+'" has been installed. Restart ssb-server to enable the plugin.\n', 'utf-8')) 147 - p.end() 148 - } 149 - ) 150 - } else 151 - p.end(new Error('"'+pluginName+'" failed to install. See log output above.')) 152 - }) 153 - return cat([ 154 - pull.values([Buffer.from('Installing "'+pluginName+'"...\n', 'utf-8')]), 155 - many([toPull(child.stdout), toPull(child.stderr)]), 156 - p 157 - ]) 158 - }, 'string', 'object?'), 159 - uninstall: valid.source(function (pluginName, opts) { 160 - var p = pushable() 161 - if (!pluginName || typeof pluginName !== 'string') 162 - return pull.error(new Error('plugin name is required')) 163 - 164 - var modulePath = path.join(installPath, 'node_modules', pluginName) 165 - 166 - rimraf(modulePath, function (err) { 167 - if (!err) { 168 - writePluginConfig(pluginName, false) 169 - p.push(Buffer.from('"'+pluginName+'" has been uninstalled. Restart ssb-server to disable the plugin.\n', 'utf-8')) 170 - p.end() 171 - } else 172 - p.end(err) 173 - }) 174 - return p 175 - }, 'string', 'object?'), 176 - enable: valid.async(configPluginEnabled(true), 'string'), 177 - disable: valid.async(configPluginEnabled(false), 'string') 178 - } 179 - } 180 - } 181 - 182 - module.exports.loadUserPlugins = function (createSsbServer, config) { 183 - // iterate all modules 184 - var nodeModulesPath = path.join(config.path, 'node_modules') 185 - //instead of testing all plugins, only load things explicitly 186 - //enabled in the config 187 - for(var module_name in config.plugins) { 188 - if(config.plugins[module_name]) { 189 - var name = config.plugins[module_name] 190 - if(name === true) 191 - name = /^ssb-/.test(module_name) ? module_name.substring(4) : module_name 192 - 193 - if (createSsbServer.plugins.some(plug => plug.name === name)) 194 - throw new Error('already loaded plugin named:'+name) 195 - var plugin = require(path.join(nodeModulesPath, module_name)) 196 - if(!plugin || plugin.name !== name) 197 - throw new Error('plugin at:'+module_name+' expected name:'+name+' but had:'+(plugin||{}).name) 198 - assertSsbServerPlugin(plugin) 199 - createSsbServer.use(plugin) 200 - } 201 - } 202 - } 203 - 204 - // predictate to check if an object appears to be a ssbServer plugin 205 - function assertSsbServerPlugin (obj) { 206 - // function signature: 207 - if (typeof obj == 'function') 208 - return 209 - 210 - // object signature: 211 - assert(obj && typeof obj == 'object', 'module.exports must be an object') 212 - assert(typeof obj.name == 'string', 'module.exports.name must be a string') 213 - assert(typeof obj.version == 'string', 'module.exports.version must be a string') 214 - assert(obj.manifest && 215 - typeof obj.manifest == 'object', 'module.exports.manifest must be an object') 216 - assert(typeof obj.init == 'function', 'module.exports.init must be a function') 217 - } 218 - 219 - function validatePluginName (name) { 220 - if (/^[._]/.test(name)) 221 - return false 222 - // from npm-validate-package-name: 223 - if (encodeURIComponent(name) !== name) 224 - return false 225 - return true 226 - } 227 - 228 - 229 -
-68
plugins/plugins.md
··· 1 - # ssb-server plugins plugin 2 - 3 - Install and manage third-party plugins. 4 - 5 - 6 - 7 - ## install: source 8 - 9 - Install a plugin to ssb-server. 10 - 11 - ```bash 12 - install {nodeModule} [--from path] 13 - ``` 14 - ```js 15 - install(nodeModule, { from: }) 16 - ``` 17 - 18 - Calls out to npm to install a package into `~/.ssb/node_modules`. 19 - 20 - - nodeModule (string): The name of the plugin to install. Uses npm's module package-name rules. 21 - - from (string): A location to install from (directory path, url, or any location that npm accepts for its install command). 22 - 23 - 24 - 25 - ## uninstall: source 26 - 27 - Uninstall a plugin from ssb-server. 28 - 29 - ```bash 30 - uninstall {nodeModule} 31 - ``` 32 - ```js 33 - uninstall(nodeModule) 34 - ``` 35 - 36 - Calls out to npm to uninstall a package into `~/.ssb/node_modules`. 37 - 38 - - nodeModule (string): The name of the plugin to uninstall. 39 - 40 - 41 - 42 - ## enable: async 43 - 44 - Update the config to enable a plugin. 45 - 46 - ```bash 47 - enable {nodeModule} 48 - ``` 49 - ```js 50 - enable(nodeModule, cb) 51 - ``` 52 - 53 - - nodeModule (string): The name of the plugin to enable. 54 - 55 - 56 - 57 - ## disable: async 58 - 59 - Update the config to disable a plugin. 60 - 61 - ```bash 62 - disable {nodeModule} 63 - ``` 64 - ```js 65 - disable(nodeModule, cb) 66 - ``` 67 - 68 - - nodeModule (string): The name of the plugin to disable.
+2 -1
scripts/pretest.js
··· 28 28 'ssb-replicate', 29 29 'ssb-ebt', 30 30 'ssb-db', 31 - 'ssb-ooo' 31 + 'ssb-ooo', 32 + 'ssb-plugins' 32 33 ] 33 34 34 35
+1
test/deps.sh
··· 26 26 all () { 27 27 28 28 test ssb-client 29 + test ssb-plugins 29 30 test ssb-friends 30 31 test ssb-blobs 31 32 test ssb-invite
-197
test/plugins.js
··· 1 - var ssbKeys = require('ssb-keys') 2 - var tape = require('tape') 3 - var u = require('./util') 4 - var pull = require('pull-stream') 5 - var osenv = require('osenv') 6 - var path = require('path') 7 - var fs = require('fs') 8 - var createSsbServer = 9 - require('secret-stack')(require('./defaults')) 10 - .use(require('ssb-db')) 11 - 12 - var initialPlugins = createSsbServer.plugins.slice() 13 - function resetSsbServer () { 14 - createSsbServer.plugins = initialPlugins.slice() 15 - createSsbServer.use(require('../plugins/plugins')) 16 - } 17 - 18 - tape('install and load plugins', function (t) { 19 - 20 - var aliceKeys = ssbKeys.generate() 21 - var datadirPath = path.join(osenv.tmpdir(), 'test-plugins1') 22 - 23 - t.test('install plugin', function (t) { 24 - 25 - resetSsbServer() 26 - var ssbServer = createSsbServer({ 27 - path: datadirPath, 28 - port: 45451, host: 'localhost', 29 - keys: aliceKeys 30 - }) 31 - 32 - console.log('installing plugin...') 33 - pull( 34 - ssbServer.plugins.install('test-plugin', { from: __dirname + '/test-plugin' }), 35 - pull.collect(function (err, out) { 36 - if (err) throw err 37 - console.log(out.map(function (b) { return b.toString('utf-8') }).join('')) 38 - 39 - t.ok(fs.statSync(path.join(datadirPath, 'node_modules/test-plugin'))) 40 - 41 - ssbServer.close(function () { 42 - t.end() 43 - }) 44 - }) 45 - ) 46 - }) 47 - 48 - t.test('installed and enabled plugin is loaded', function (t) { 49 - 50 - var config = { 51 - path: datadirPath, 52 - port: 45451, host: 'localhost', 53 - keys: aliceKeys, 54 - plugins: { 55 - 'test-plugin': 'test' 56 - } 57 - } 58 - resetSsbServer() 59 - require('../plugins/plugins').loadUserPlugins(createSsbServer, config) 60 - var ssbServer = createSsbServer(config) 61 - 62 - t.ok(ssbServer.test) 63 - t.ok(ssbServer.test.ping) 64 - 65 - ssbServer.test.ping('ping', function (err, res) { 66 - if (err) throw err 67 - t.equal(res, 'ping pong') 68 - 69 - ssbServer.close(function () { 70 - t.end() 71 - }) 72 - }) 73 - }) 74 - 75 - t.test('installed and disabled plugin is not loaded', function (t) { 76 - 77 - var config = { 78 - path: datadirPath, 79 - port: 45451, host: 'localhost', 80 - keys: aliceKeys, 81 - plugins: { 82 - 'test-plugin': false 83 - } 84 - } 85 - resetSsbServer() 86 - require('../plugins/plugins').loadUserPlugins(createSsbServer, config) 87 - var ssbServer = createSsbServer(config) 88 - 89 - t.equal(ssbServer.test, undefined) 90 - 91 - ssbServer.close(function () { 92 - t.end() 93 - }) 94 - }) 95 - 96 - t.test('uninstall plugin', function (t) { 97 - 98 - resetSsbServer() 99 - var ssbServer = createSsbServer({ 100 - path: datadirPath, 101 - port: 45451, host: 'localhost', 102 - keys: aliceKeys 103 - }) 104 - 105 - console.log('uninstalling plugin...') 106 - pull( 107 - ssbServer.plugins.uninstall('test-plugin'), 108 - pull.collect(function (err, out) { 109 - if (err) throw err 110 - console.log(out.map(function (b) { return b.toString('utf-8') }).join('')) 111 - 112 - t.throws(function () { fs.statSync(path.join(datadirPath, 'node_modules/test-plugin')) }) 113 - 114 - ssbServer.close(function () { 115 - t.end() 116 - }) 117 - }) 118 - ) 119 - }) 120 - 121 - t.test('install plugin under custom name', function (t) { 122 - 123 - resetSsbServer() 124 - var ssbServer = createSsbServer({ 125 - path: datadirPath, 126 - port: 45451, host: 'localhost', 127 - keys: aliceKeys 128 - }) 129 - 130 - console.log('installing plugin...') 131 - pull( 132 - ssbServer.plugins.install('my-test-plugin', { from: __dirname + '/test-plugin' }), 133 - pull.collect(function (err, out) { 134 - if (err) throw err 135 - console.log(out.map(function (b) { return b.toString('utf-8') }).join('')) 136 - 137 - t.ok(fs.statSync(path.join(datadirPath, 'node_modules/my-test-plugin'))) 138 - 139 - ssbServer.close(function () { 140 - t.end() 141 - }) 142 - }) 143 - ) 144 - }) 145 - 146 - t.test('installed and enabled plugin is loaded, under custom name', function (t) { 147 - 148 - var config = { 149 - path: datadirPath, 150 - port: 45451, host: 'localhost', 151 - keys: aliceKeys, 152 - plugins: { 153 - 'my-test-plugin': 'test' 154 - } 155 - } 156 - resetSsbServer() 157 - require('../plugins/plugins').loadUserPlugins(createSsbServer, config) 158 - var ssbServer = createSsbServer(config) 159 - 160 - t.ok(ssbServer.test) 161 - t.ok(ssbServer.test.ping) 162 - 163 - ssbServer.test.ping('ping', function (err, res) { 164 - if (err) throw err 165 - t.equal(res, 'ping pong') 166 - 167 - ssbServer.close(function () { 168 - t.end() 169 - }) 170 - }) 171 - }) 172 - 173 - t.test('uninstall plugin under custom name', function (t) { 174 - 175 - resetSsbServer() 176 - var ssbServer = createSsbServer({ 177 - path: datadirPath, 178 - port: 45451, host: 'localhost', 179 - keys: aliceKeys 180 - }) 181 - 182 - console.log('uninstalling plugin...') 183 - pull( 184 - ssbServer.plugins.uninstall('my-test-plugin'), 185 - pull.collect(function (err, out) { 186 - if (err) throw err 187 - console.log(out.map(function (b) { return b.toString('utf-8') }).join('')) 188 - 189 - t.throws(function () { fs.statSync(path.join(datadirPath, 'node_modules/my-test-plugin')) }) 190 - 191 - ssbServer.close(function () { 192 - t.end() 193 - }) 194 - }) 195 - ) 196 - }) 197 - })