├── .github └── stale.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── default-ports.js ├── defaults.js ├── index.js ├── inject.js ├── package.json ├── tests ├── connections.test.js ├── defaults.test.js ├── inject.test.js ├── server-startup.test.js └── server │ ├── custom.config.js │ ├── default.config.js │ ├── secret-stack │ ├── custom.js │ ├── default.js │ └── index.js │ └── ssb-server │ ├── custom.js │ ├── default.js │ └── index.js └── util ├── fix-connections.js ├── get-net.js ├── get-ws.js └── incoming-connections.js /.github/stale.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use Node.js configuration. 2 | language: node_js 3 | 4 | node_js: 5 | - 10 6 | - 12 7 | - node # latest 8 | 9 | os: 10 | - linux 11 | - osx 12 | - windows 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dominic Tarr 4 | 5 | Permission is hereby granted, free of charge, 6 | to any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), to 8 | deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, 10 | merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom 12 | the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 22 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssb-config 2 | 3 | This module helps you to generate and manipulate the startup configuration for an 4 | [`ssb-server`](https://github.com/ssbc/ssb-server). 5 | 6 | ## Table of contents 7 | [Example usage](#example-usage) | [Api](#api) | [Configuration](#configuration) | [License](#license) 8 | 9 | ___ 10 | 11 | ## Example usage 12 | This is the most use basic use case where it is not necessary to modify any configuration parameters. 13 | 14 | ``` js 15 | var Server = require('ssb-server') 16 | var config = require('ssb-config') 17 | 18 | var server = Server(config) 19 | server.whoami((err, feed) => { 20 | console.log(feed) 21 | 22 | server.close(() => console.log('closing the server!')) 23 | }) 24 | ``` 25 | If you want to change the default values you can use inject to overwrite them, without having to specify 26 | all the settings. For example you can setup a test network that doesn't collide with the main ssb network: 27 | 28 | ```js 29 | var Server = require('ssb-server') 30 | var Config = require('ssb-config/inject') 31 | 32 | var config = Config('testnet', { port: 9999 }) 33 | 34 | var server = Server(config) 35 | server.whoami((err, feed) => { 36 | console.log(feed) 37 | 38 | server.close(() => console.log('closing the server!')) 39 | }) 40 | ``` 41 | 42 | ## API 43 | 44 | ### `require('ssb-config')` 45 | 46 | Returns you the stock standard config for starting an __ssb-server__. 47 | 48 | ### `require('ssb-config/inject')(appName, opts) => Object` 49 | 50 | A function which takes: 51 | - `appName` *(string)* Which declares where to look for further config, where to read and write databases. 52 | Stores data in `~/.${appName}`, defaults to `ssb` (so data in `~/.ssb`). 53 | - `opts` *(object)* An object which can override config defaults (see *[Configuration](#configuration)* below). 54 | 55 | ## Configuration 56 | 57 | All configuration is loaded via `rc`. This means the final config is a result of config collected from `opts` 58 | passed into the inject method, cli args, env var, and config (e.g. `~/.ssb/config`). See the 59 | [rc repo](https://github.com/dominictarr/rc) for full details. 60 | 61 | __Options__ 62 | * `connections` *(object)* Details `incoming` and `outgoing` connections behaviour ([See below](#connections)). 63 | * `remote` *(string)* [Multiserver address](https://github.com/ssbc/multiserver#address-format) to connect as 64 | a client. Useful in some cases, such as using [ssb-unix-socket](https://github.com/ssbc/ssb-unix-socket) + 65 | [ssb-no-auth](https://github.com/ssbc/ssb-no-auth). In the future this may be deprecated / derived from 66 | `connections`. 67 | * `timeout`: *(number)* Number of milliseconds a replication stream can idle before it's automatically 68 | disconnected. Defaults to `30000`. 69 | * `pub` *(boolean)* Replicate with pub servers. Defaults to `true`. 70 | * `local` *(boolean)* Replicate with local servers found on the same network via `udp`. Defaults to `true`. 71 | * `friends.dunbar` *(number)* [`Dunbar's number`](https://en.wikipedia.org/wiki/Dunbar%27s_number). Number 72 | of nodes your instance will replicate. Defaults to `150`. 73 | * `friends.hops` *(number)* How many friend of friend hops to replicate. Defaults to `3`. 74 | * `gossip` *(object)* Controls what sort of connections are made ([See below](#gossip)). 75 | * `path` *(string)* Path to the application data folder, which contains the private key, message attachment 76 | data (blobs) and the leveldb backend. Defaults to `$HOME/.ssb`. 77 | * `master` *(array)* Pubkeys of users who, if they connect to the [ssb-server](https://github.com/ssbc/ssb-server) 78 | instance, are allowed to command the primary user with full rights. Useful for remotely operating a pub. 79 | Defaults to `[]`. 80 | * `logging.level` *(string)* How verbose should the logging be. Possible values are error, warning, notice, 81 | and info. Defaults to `notice`. 82 | * `party.out` _(string)_ Where to put standard output of sbot. may be a path (absolute, or relative to ssb's 83 | directory), or false to discard, or true to pass through to the controlling terminal. Defaults to true. 84 | * `party.err`_(string)_ Where to put standard error of sbot. Defaults to same as config.party.out. 85 | * `timers.connection` _(number)_ TODO 86 | * `timers.reconnect` _(number)_ TODO 87 | * `timers.inactivity` _(number)_ Timeout (ms) before dropping the connection with an inactive pair. 88 | Defaults to 5 seconds. 89 | * `timers.ping` _(number)_ Timeout (ms) used to consider a peer valid when pinging. Defaults to 5 minutes. 90 | * `timers.handshake` _(number)_ Maximum waiting time (ms) for a handshake response. Defaults to 5 seconds. 91 | * `timers.keepalive` _(number)_ Minimum time (ms) to keep the server online after the last client disconnects. 92 | Defaults to 30s. 93 | * `caps.shs` _(string)_ Key for accessing the scuttlebutt protocol 94 | (see [secret-handshake paper](https://github.com/ssbc/ssb-caps/) for a full explaination). 95 | * `caps.sign` _(string)_ Used to sign messages. 96 | 97 | __Deprecated Options__ 98 | * `host` *(string)* The domain or ip address for [ssb-server](https://github.com/ssbc/ssb-server). Defaults to 99 | your public ip address. 100 | * `port` *(string|number)* The port for [ssb-server](https://github.com/ssbc/ssb-server). Defaults to `8008`. 101 | * `ws` TODO 102 | 103 | You should use `connections` to more explicitly configure connections. 104 | These values are currently only used to generate `connections.incoming` if that option isn't provided. 105 | The raw options are no longer returned in the final config - this is to ensure we don't have multiple places 106 | where different `host` / `port` / `ws` are being set! 107 | 108 | ### `connections` 109 | 110 | An object with two required properties: `incoming` and `outgoing` to specify transports and transformations 111 | for connections. Defaults to the following: 112 | ```json 113 | { 114 | "incoming": { 115 | "net": [{ "port": 8008, "scope": "public", "transform": "shs" }] 116 | }, 117 | "outgoing": { 118 | "net": [{ "transform": "shs" }], 119 | "onion": [{ "transform": "shs" }] 120 | } 121 | } 122 | ``` 123 | 124 | It specifies the default TCP `net`work transport for incoming and outging connections, 125 | using secret-handshake/boxstream ([shs](https://github.com/auditdrivencrypto/secret-handshake)) for 126 | authentication and encryption. 127 | 128 | A **transport** is a vehicle or avenue for communication. The following transports are currently supported: 129 | - `net` - TCP based 130 | - `unix` - socket based 131 | - `onion` - TOR based 132 | - `ws` - websocket based 133 | 134 | Each transport can have an array of different configurations passed to it, these are objects with properties: 135 | - `transform` *(string)* Determines whether traffic is encrypted, and if so how. 136 | - `shs` - Secret handashake. 137 | - `noauth` - No encryption, any connection via `noauth` is considered authorized. **use only with `device` 138 | scope or unix socket**. 139 | - `port` _(integer)_ 140 | - `host` _(string)_ IP or hostname that the listener is binding on. 141 | - `scope` _(string | array of string)_ scope Determines the set of network interfaces to bind the server to. 142 | If scope is an array, then the server will bind to all the selected ports. [See more about scopes below](#scopes). 143 | - `external` _(array of strings)_ For use in combination with public scope. this is the external domain given 144 | out as the address to peers. 145 | - `key` _(string)_ Used together with `cert` for ws plugin to run over TLS (wss). Needs to be a path to where 146 | the key is stored. 147 | - `cert` _(sitring)_ Used together with `key` for ws plugin to run over TLS (wss). Needs to be a path to where 148 | the certificate is stored. 149 | 150 | #### Scopes 151 | 152 | An address scope is the area from which it's possible to connect to an address. 153 | * `device` Means connections can only come from the same device. (talking to your self). _alias `private`_. 154 | * `local` Means connections can only come from the same network, i.e. same wifi. 155 | * `public` Means connections can come from anywhere on the internet. 156 | 157 | Some protocols only work in particular scopes. `unix` socket requires file system access, 158 | so it only works for the device scope. `onion` (tor) routes connections through a distributed network, so it 159 | only works if you are fully connected to the `public` internet. Some mesh networks are really large, so they 160 | might seem public. Some overlay networks, such as [cjdns](https://github.com/cjdelisle/cjdns/) and 161 | [ZeroTier](https://www.zerotier.com/) create a fake local network that is publically accessible 162 | (these should be manually configured as public addresses!). 163 | 164 | Most ssb peers just have a local and device scopes. Pubs require a public scope. 165 | `ssb-tunnel` allows any peer to have a public address, by routing connections through a friendly pub. 166 | 167 | Addresses for scopes are provides `secret-stack`s `getAddress(scope)` method, which in turn calls 168 | `multiserver`s `stringify(scope)` method. 169 | 170 | ### Example `connnections` configurations 171 | 172 | If you only want to use [Tor](https://torproject.org) to create outgoing connections you can specify your 173 | `outgoing` like this. It will use `localhost:9050` as the socks server for creating this. 174 | 175 | ```json 176 | { 177 | "incoming": { 178 | "net": [{ "port": 8008, "scope": "public", "transform": "shs" }] 179 | }, 180 | "outgoing": { 181 | "onion": [{ "transform": "shs" }] 182 | } 183 | } 184 | ``` 185 | 186 | If you want to run a peer behind NAT or other kind of proxy but still want 187 | [ssb-server](https://github.com/ssbc/ssb-server) to be able to create invites for the outside address, you 188 | can specify a `public` scope as your `incoming.net` by defining the `external` parameter like this: 189 | 190 | ```json 191 | { 192 | "incoming": { 193 | "net": [ 194 | { "scope": "public", "external": ["cryptop.home"], "transform": "shs", "port": 8008 }, 195 | { "scope": "private", "transform": "shs", "port": 8008, "host": "internal1.con.taine.rs" }, 196 | ] 197 | }, 198 | "outgoing": { 199 | "net": [{ "transform": "shs" }] 200 | } 201 | } 202 | ``` 203 | 204 | One thing to notice is that you _need_ `incoming` connections for Apps (like patchwork or git-ssb) to 205 | function. By default they use the same authentication mechanism (shs) to grant access to the database, 206 | choosing access levels depending on the keypair that opens the connection. If you connect to yourself, 207 | you get full access (query and publish). If a remote peer connects, it can only replicate. So be sure 208 | to have **at least one** `incoming` connection. 209 | 210 | That being said, the overhead of encryption for local applications can be very high, especially on 211 | low-powered devices. For this use-case there is a `noauth` transform which by-passes the authentication 212 | and grants full access to anybody that can connect to it. **hint:** *This is risky! it might expose 213 | private messages or enables people to publish as you!* Therefore be sure to bind the listener to 214 | `localhost` or use the `unix` socket. The `unix` file socket is created as `$HOME/.ssb/socket` by 215 | default and has permissions such that only the user running `ssb-server start` can open it, just like 216 | the `$HOME/.ssb/secret` file. 217 | 218 | ```json 219 | { 220 | "incoming": { 221 | "unix": [{ "scope":"device", "transform":"noauth" }], 222 | "net": [{ "scope": "device", "transform": "noauth", "port": 8009, "host": "localhost" }] 223 | }, 224 | "outgoing": { 225 | "net": [{ "transform": "shs" }] 226 | } 227 | } 228 | ``` 229 | 230 | The local plugin inside [ssb-server](https://github.com/ssbc/ssb-server) will use the first incoming 231 | connection of either public or private scope. 232 | 233 | ### `gossip` 234 | Set which sorts of gossip connections are permitted: 235 | 236 | - `connections` *(number)* How many other nodes to connect with at one time. Defaults to `2`. 237 | - `local` *(boolean)* Make gossip connections with peers on the same local network as you. 238 | - `friends` *(boolean)* Make gossip connections with peers who are friends. 239 | - `seed` *(boolean)* Make gossip connection with manually added seeds, it is generally used in tests. 240 | - `global` *(boolean)* Don't restrict the connections but prioritize the connections to the peers you're 241 | friends with. 242 | 243 | For example, allow only gossip connections with peers found on the same local network as you, but prioritize 244 | connections with friends: 245 | 246 | ```js 247 | { 248 | gossip: { 249 | connections: 3, 250 | local: true, 251 | friends: false, 252 | seed: false, 253 | global: true 254 | } 255 | } 256 | ``` 257 | ___ 258 | ## License 259 | 260 | MIT 261 | -------------------------------------------------------------------------------- /default-ports.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | net: 8008, 3 | ws: 8989 4 | } 5 | -------------------------------------------------------------------------------- /defaults.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var home = require('os-homedir') 3 | var merge = require('deep-extend') 4 | var ssbCaps = require('ssb-caps') 5 | var ssbKeys = require('ssb-keys') 6 | 7 | var incomingConnections = require('./util/incoming-connections') 8 | var fixConnections = require('./util/fix-connections') 9 | 10 | var SEC = 1e3 11 | var MIN = 60 * SEC 12 | 13 | module.exports = function setDefaults (name, config) { 14 | var baseDefaults = { 15 | path: path.join(home() || 'browser', '.' + name), 16 | party: true, 17 | timeout: 0, 18 | pub: true, 19 | local: true, 20 | friends: { 21 | dunbar: 150, 22 | hops: 2 23 | }, 24 | gossip: { 25 | connections: 3 26 | }, 27 | connections: { 28 | outgoing: { 29 | net: [{ transform: 'shs' }], 30 | onion: [{ transform: 'shs' }] 31 | } 32 | }, 33 | timers: { 34 | connection: 0, 35 | reconnect: 5 * SEC, 36 | ping: 5 * MIN, 37 | handshake: 5 * SEC 38 | }, 39 | // change these to make a test network that will not connect to the main network. 40 | caps: ssbCaps, 41 | master: [], 42 | logging: { level: 'notice' } 43 | } 44 | config = merge(baseDefaults, config || {}) 45 | 46 | if (!config.connections.incoming) { 47 | // if no incoming connections have been set, 48 | // populate this with some rad-comprehensive defaults! 49 | config.connections.incoming = incomingConnections(config) 50 | } 51 | 52 | config = fixConnections(config) 53 | 54 | if (config.keys == null) { 55 | config.keys = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret')) 56 | } 57 | 58 | return config 59 | } 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./inject')() 2 | -------------------------------------------------------------------------------- /inject.js: -------------------------------------------------------------------------------- 1 | var RC = require('rc') 2 | var setDefaults = require('./defaults') 3 | 4 | module.exports = function (name, override) { 5 | name = name || 'ssb' 6 | var rc = RC(name, override || {}) 7 | var config = setDefaults(name, rc) 8 | return config 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssb-config", 3 | "description": "load ssb config", 4 | "version": "3.4.6", 5 | "homepage": "https://github.com/ssbc/ssb-config", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/ssbc/ssb-config.git" 9 | }, 10 | "scripts": { 11 | "test": "tape tests/*.test.js | tap-spec", 12 | "test:lite": "SKIP_SERVER=true npm test" 13 | }, 14 | "dependencies": { 15 | "deep-extend": "^0.6.0", 16 | "ip": "^1.1.5", 17 | "lodash.get": "^4.4.2", 18 | "os-homedir": "^1.0.1", 19 | "rc": "^1.1.6", 20 | "ssb-caps": "^1.0.1", 21 | "ssb-keys": "^8.2.0" 22 | }, 23 | "devDependencies": { 24 | "scuttle-testbot": "^1.2.3", 25 | "ssb-client": "^4.7.1", 26 | "ssb-master": "^1.0.3", 27 | "ssb-server": "^16.0.1", 28 | "tap-spec": "^5.0.0", 29 | "tape": "^4.9.2" 30 | }, 31 | "author": "Dominic Tarr (http://dominictarr.com)", 32 | "license": "MIT" 33 | } 34 | -------------------------------------------------------------------------------- /tests/connections.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | var Config = require('../inject') 4 | 5 | test('setting custom host:port', t => { 6 | var config = Config('testnet', { 7 | host: 'pub.mixmix.io', 8 | port: 2001 9 | }) 10 | 11 | var { host, port } = config.connections.incoming.net[0] 12 | t.equal(host, 'pub.mixmix.io', 'net: sets custom host in connections') 13 | t.equal(port, 2001, 'net: sets custom port in connections') 14 | 15 | t.equal(config.host, 'pub.mixmix.io', 'net: [LEGACY] custom config.host is set') 16 | t.equal(config.port, 2001, 'net: [LEGACY] custom config.port is set') 17 | 18 | var { host: WSHost, port: WSPort } = config.connections.incoming.ws[0] 19 | t.equal(WSHost, 'pub.mixmix.io', 'ws: sets custom host in connections') 20 | t.equal(WSPort, 8989, 'ws: sets default port in connections') 21 | 22 | t.equal(config.ws.port, 8989, 'ws: [LEGACY] custom config.ws.port is set') 23 | 24 | t.end() 25 | }) 26 | 27 | test('setting custom connections.incoming', t => { 28 | var config = Config('testnet', { 29 | connections: { 30 | incoming: { 31 | net: [{ host: 'pub.mixmix.io', port: 23456, scope: 'public' }], 32 | ws: [{ host: 'pub.mixmix.io', port: 23457, scope: 'public' }] 33 | } 34 | } 35 | }) 36 | 37 | var { host, port } = config.connections.incoming.net[0] 38 | t.equal(host, 'pub.mixmix.io', 'net: sets custom host in connections') 39 | t.equal(port, 23456, 'net: sets custom port in connections') 40 | 41 | t.equal(config.host, 'pub.mixmix.io', 'net: [LEGACY] custom config.host is set') 42 | t.equal(config.port, 23456, 'net: [LEGACY] custom config.port is set') 43 | 44 | var { host: WSHost, port: WSPort } = config.connections.incoming.ws[0] 45 | t.equal(WSHost, 'pub.mixmix.io', 'ws: sets custom host in connections') 46 | t.equal(WSPort, 23457, 'ws: sets default port in connections') 47 | 48 | t.equal(config.ws.port, 23457, 'ws: [LEGACY] custom config.ws.port is set') 49 | 50 | t.end() 51 | }) 52 | 53 | test('CONFLICTING custom host:port connections.incoming settings', t => { 54 | var netHost = () => { 55 | Config('testnet', { 56 | host: 'peach.party', 57 | connections: { 58 | incoming: { 59 | net: [{ host: 'pub.mixmix.io', port: 23456, scope: 'public' }] 60 | } 61 | } 62 | }) 63 | } 64 | 65 | var netPort = () => { 66 | Config('testnet', { 67 | host: 'pub.mixmix.io', 68 | port: 2019, 69 | connections: { 70 | incoming: { 71 | net: [{ host: 'pub.mixmix.io', port: 23456, scope: 'public' }], 72 | ws: [{ host: 'pub.mixmix.io', port: 23457, scope: 'public' }] 73 | } 74 | } 75 | }) 76 | } 77 | var wsPort = () => { 78 | Config('testnet', { 79 | ws: { port: 2019 }, 80 | connections: { 81 | incoming: { 82 | net: [{ host: 'pub.mixmix.io', port: 23456, scope: 'public' }], 83 | ws: [{ host: 'pub.mixmix.io', port: 23457, scope: 'public' }] 84 | } 85 | } 86 | }) 87 | } 88 | 89 | function testThrow (fn, target) { 90 | try { 91 | fn() 92 | } catch (e) { 93 | var expectedMessage = `ssb-config: conflicting connection settings for: ${target}` 94 | t.equal(e.message, expectedMessage, `catches conflicting ${target} settings`) 95 | } 96 | } 97 | testThrow(netHost, 'net host') 98 | testThrow(netPort, 'net port') 99 | testThrow(wsPort, 'ws port') 100 | 101 | // mix: I know t.throws exists, but testing the error message output was annoying 102 | 103 | t.end() 104 | }) 105 | 106 | test('setting connections.incoming explicitly with no ws', t => { 107 | // because we don't want websockets active 108 | 109 | var config = Config('testnet', { 110 | connections: { 111 | incoming: { 112 | net: [{ host: 'pub.mixmix.io', port: 23456, scope: 'public' }] 113 | // ws: [{ host: 'pub.mixmix.io', port: 23457, scope: 'public' }] 114 | } 115 | } 116 | }) 117 | 118 | t.equal(config.connections.incoming.ws, undefined, 'no ws set in connection.incoming') 119 | t.equal(config.ws.port, undefined, 'ws: [LEGACY] no config.ws.port set') 120 | t.equal(config.host, 'pub.mixmix.io', 'host still derived from net setting') 121 | 122 | t.end() 123 | }) 124 | 125 | test('incoming net connection has no port configured', t => { 126 | var config = Config('testnet', { 127 | connections: { 128 | incoming: { 129 | net: [{ host: 'pub.mixmix.io', scope: 'public' }] 130 | } 131 | } 132 | }) 133 | 134 | var { port } = config.connections.incoming.net[0] 135 | t.equal(port, 8008, 'net: sets default port in connections') 136 | t.equal(config.port, 8008, 'net: [LEGACY] default config.port is set') 137 | 138 | t.end() 139 | }) 140 | 141 | test('incoming ws connection has no port configured', t => { 142 | var config = Config('testnet', { 143 | connections: { 144 | incoming: { 145 | ws: [{ host: 'pub.mixmix.io', scope: 'public' }] 146 | } 147 | } 148 | }) 149 | 150 | var { port } = config.connections.incoming.ws[0] 151 | t.equal(port, 8989, 'ws: sets default port in connections') 152 | t.equal(config.ws.port, 8989, 'ws: [LEGACY] default config.ws.port is set') 153 | 154 | t.end() 155 | }) 156 | 157 | test('port is set on top level, but not incoming', t => { 158 | var config = Config('testnet', { 159 | port: 8009, 160 | host: 'localhost', 161 | connections: { 162 | incoming: { 163 | net: [{ scope: ['device', 'local'], host: 'localhost' }] 164 | } 165 | } 166 | }) 167 | console.log(config.connections.incoming) 168 | 169 | t.equal(config.port, 8009, 'net: sets default port in connections') 170 | 171 | t.end() 172 | }) 173 | -------------------------------------------------------------------------------- /tests/defaults.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const Config = require('../defaults') 3 | 4 | test('defaults', t => { 5 | const defaults = { 6 | port: 8899, 7 | keys: 'placeholder' 8 | } 9 | const config = Config('testnet', defaults) 10 | 11 | t.equal(config.port, defaults.port, 'custom ports preserved') 12 | // this test was failing when there was no network connection, 13 | // was returning undefined 14 | 15 | t.end() 16 | }) 17 | -------------------------------------------------------------------------------- /tests/inject.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const ssbKeys = require('ssb-keys') 3 | 4 | const Config = require('../inject') 5 | 6 | test('default: no keys set', t => { 7 | const config = Config('testnet') 8 | 9 | t.ok(config.keys.public, 'keys exist') 10 | t.end() 11 | }) 12 | 13 | test('custom: keys injected', t => { 14 | const keys = ssbKeys.generate() 15 | const config = Config('testnet', { keys }) 16 | 17 | t.equal(keys.public, config.keys.public, 'keys exist') 18 | t.end() 19 | }) 20 | -------------------------------------------------------------------------------- /tests/server-startup.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | const { join } = require('path') 3 | const Client = require('ssb-client') 4 | const home = require('os-homedir')() 5 | 6 | var { fork } = require('child_process') 7 | 8 | var configCustom = require('./server/custom.config.js') 9 | 10 | if (process.env.SKIP_SERVER) { 11 | console.log('\n!! skipping server startup tests\n') 12 | // HACK: this skips the tests as an alternative to a global `return` 13 | test = function () {} 14 | } 15 | 16 | function checkStartUp ({ t, processPath, config }) { 17 | const server = fork(join(__dirname, 'server', processPath)) 18 | // const server = fork(Path.join(__dirname, '../server.js'), { detached: true }) 19 | 20 | server.on('message', msg => { 21 | if (msg.action === 'KILLME') server.kill() 22 | if (msg.action === 'READY') { 23 | Client(config.keys, config, (err, ssb) => { 24 | if (err) { 25 | console.log(err) 26 | 27 | server.send({ action: 'CLOSE' }) 28 | server.kill() 29 | } 30 | 31 | t.false(err, 'remote connection to server works') 32 | 33 | ssb.whoami((_, feed) => { 34 | if (err) throw err 35 | t.true(feed.id.startsWith('@'), 'remote query works') 36 | 37 | ssb.close(() => { 38 | server.send({ action: 'CLOSE' }) 39 | t.end() 40 | }) 41 | }) 42 | }) 43 | } 44 | }) 45 | 46 | server.send({ action: 'READY' }) 47 | } 48 | 49 | test('server startup', t => { 50 | t.test('ssb-server, default config', t => { 51 | const config = require('./server/default.config.js') 52 | const processPath = 'ssb-server/default.js' 53 | 54 | t.equal(config.path, join(home, '.ssb'), 'has default ~/.ssb folder') 55 | t.equal(config.connections.incoming.net[0].port, 8008, 'e.g. has default port') 56 | t.equal(config.friends.dunbar, 150, 'e.g. has default dunbar number') 57 | 58 | checkStartUp({ t, config, processPath }) 59 | }) 60 | 61 | t.test('ssb-server, custom config', t => { 62 | const config = require('./server/custom.config.js') 63 | const processPath = 'ssb-server/custom.js' 64 | 65 | t.equal(configCustom.path, join(home, '.testnet'), 'adjusts path to match appname') 66 | t.equal(configCustom.connections.incoming.net[0].port, 9999, 'e.g. has default port') 67 | t.equal(configCustom.friends.dunbar, 1500, 'has new default dunbar number') 68 | 69 | checkStartUp({ t, config, processPath }) 70 | }) 71 | 72 | t.test('secret-stack, default config', t => { 73 | const config = require('./server/default.config.js') 74 | const processPath = 'secret-stack/default.js' 75 | checkStartUp({ t, config, processPath }) 76 | }) 77 | 78 | t.test('secret-stack, custom config', t => { 79 | const config = require('./server/custom.config.js') 80 | const processPath = 'secret-stack/custom.js' 81 | checkStartUp({ t, config, processPath }) 82 | }) 83 | 84 | t.end() 85 | }) 86 | -------------------------------------------------------------------------------- /tests/server/custom.config.js: -------------------------------------------------------------------------------- 1 | const Config = require('../../inject') 2 | 3 | module.exports = Config('testnet', { port: 9999, friends: { dunbar: 1500 } }) 4 | -------------------------------------------------------------------------------- /tests/server/default.config.js: -------------------------------------------------------------------------------- 1 | const Defaults = require('../../defaults') 2 | 3 | module.exports = Defaults('ssb') 4 | // NOTE: ideally would use index.js, but on a system with ~/.ssb/config, 5 | // this best simulates a clean setup by side-stepping RC 6 | -------------------------------------------------------------------------------- /tests/server/secret-stack/custom.js: -------------------------------------------------------------------------------- 1 | const Server = require('./') 2 | const config = require('../custom.config.js') 3 | 4 | Server(config) 5 | -------------------------------------------------------------------------------- /tests/server/secret-stack/default.js: -------------------------------------------------------------------------------- 1 | const Server = require('./') 2 | const config = require('../default.config.js') 3 | 4 | Server(config) 5 | -------------------------------------------------------------------------------- /tests/server/secret-stack/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const Path = require('path') 3 | 4 | const Server = require('scuttle-testbot') 5 | .use(require('ssb-master')) 6 | 7 | module.exports = function TestServer (config) { 8 | const server = Server(config) 9 | writeManifest(server) 10 | 11 | process.on('message', msg => { 12 | if (msg.action === 'CLOSE') { 13 | server.close(() => { 14 | console.log('CLOSING') 15 | process.send({ action: 'KILLME' }) 16 | }) 17 | } 18 | }) 19 | 20 | server.whoami((err, data) => { 21 | if (err) { 22 | server.close() 23 | throw err 24 | } 25 | 26 | console.log('> server started') 27 | setTimeout(() => process.send({ action: 'READY' }), 5) 28 | }) 29 | } 30 | 31 | function writeManifest (server) { 32 | fs.writeFileSync( 33 | Path.join(server.config.path, 'manifest.json'), 34 | JSON.stringify(server.getManifest()) 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /tests/server/ssb-server/custom.js: -------------------------------------------------------------------------------- 1 | const Server = require('./') 2 | const config = require('../custom.config.js') 3 | 4 | Server(config) 5 | -------------------------------------------------------------------------------- /tests/server/ssb-server/default.js: -------------------------------------------------------------------------------- 1 | const Server = require('./') 2 | const config = require('../default.config.js') 3 | 4 | Server(config) 5 | -------------------------------------------------------------------------------- /tests/server/ssb-server/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const Path = require('path') 3 | 4 | const Server = require('ssb-server') 5 | .use(require('ssb-master')) 6 | 7 | module.exports = function TestServer (config) { 8 | const server = Server(config) 9 | writeManifest(server) 10 | 11 | process.on('message', msg => { 12 | if (msg.action === 'CLOSE') { 13 | server.close(() => { 14 | console.log('CLOSING') 15 | process.send({ action: 'KILLME' }) 16 | }) 17 | } 18 | }) 19 | 20 | server.whoami((err, data) => { 21 | if (err) { 22 | server.close() 23 | throw err 24 | } 25 | 26 | console.log('> server started') 27 | setTimeout(() => process.send({ action: 'READY' }), 5) 28 | }) 29 | } 30 | 31 | function writeManifest (server) { 32 | fs.writeFileSync( 33 | Path.join(server.config.path, 'manifest.json'), 34 | JSON.stringify(server.getManifest()) 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /util/fix-connections.js: -------------------------------------------------------------------------------- 1 | var getNet = require('./get-net') 2 | var getWS = require('./get-ws') 3 | var defaultPorts = require('../default-ports') 4 | 5 | // *** BACKSTORY *** 6 | // there has been an evolution of how connection host:port and ws are set 7 | // historically you have been able to set host, port net connections, 8 | // and we want to keep this behaviour to support legacy code, and easy CLI setting 9 | // 10 | // we also want to support the new connections.incoming style 11 | // this code: 12 | // - checks that a user is not declaring conflicting settings 13 | // (so new/ old parts of the stack don't run divergent config!) 14 | // - writes host,port,ws settings based on the connections.incoming setting 15 | 16 | module.exports = function fixConnections (config) { 17 | const net = getNet(config) || {} 18 | const ws = getWS(config) || {} 19 | 20 | // Add default ports 21 | if (net.host && !net.port) net.port = config.port || defaultPorts.net 22 | if (ws.host && !ws.port) ws.port = config.port || defaultPorts.ws 23 | 24 | // [LEGACY] ensure host:port + ws are set 25 | var errors = [] 26 | if (config.host && net.host) { 27 | if (config.host !== net.host) errors.push('net host') 28 | } 29 | if (config.port && net.port) { 30 | if (config.port !== net.port) errors.push('net port') 31 | } 32 | if (config.ws && config.ws.port && ws.port) { 33 | if (config.ws.port !== ws.port) errors.push('ws port') 34 | } 35 | 36 | if (errors.length) { 37 | const message = 'ssb-config: conflicting connection settings for: ' + errors.join(', ') 38 | throw new Error(message) 39 | } 40 | 41 | // LEGACY - ensure host and port are set 42 | // (but based on new connections config style) 43 | config.host = net.host || ws.host 44 | config.port = net.port 45 | config.ws = ws 46 | 47 | return config 48 | } 49 | -------------------------------------------------------------------------------- /util/get-net.js: -------------------------------------------------------------------------------- 1 | var get = require('lodash.get') 2 | 3 | module.exports = function getNet (config) { 4 | const conns = get(config, 'connections.incoming.net', []) 5 | 6 | return ( 7 | conns.find(isPublic) || 8 | conns.find(isLocal) || 9 | conns.find(isDevice) 10 | ) 11 | } 12 | 13 | function isPublic (transport) { 14 | const scope = 'public' // internet 15 | 16 | return ( 17 | transport.scope === scope || 18 | transport.scope.includes(scope) 19 | ) 20 | } 21 | 22 | function isLocal (transport) { 23 | const scopes = [ 24 | 'local', // local wifi 25 | 'private' // (alias of local) 26 | ] 27 | 28 | return scopes.some(s => { 29 | return transport.scope === s || transport.scope.includes(s) 30 | }) 31 | } 32 | 33 | function isDevice (transport) { 34 | const scope = 'device' // local device only 35 | 36 | return ( 37 | transport.scope === scope || 38 | transport.scope.includes(scope) 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /util/get-ws.js: -------------------------------------------------------------------------------- 1 | var get = require('lodash.get') 2 | 3 | module.exports = function getNet (config) { 4 | const conns = get(config, 'connections.incoming.ws', []) 5 | 6 | return ( 7 | conns.find(isPublic) || 8 | conns.find(isLocal) || 9 | conns.find(isDevice) 10 | ) 11 | } 12 | 13 | function isPublic (transport) { 14 | const scope = 'public' // internet 15 | 16 | return ( 17 | transport.scope === scope || 18 | transport.scope.includes(scope) 19 | ) 20 | } 21 | 22 | function isLocal (transport) { 23 | const scopes = [ 24 | 'local', // local wifi 25 | 'private' // (alias of local) 26 | ] 27 | 28 | return scopes.some(s => { 29 | return transport.scope === s || transport.scope.includes(s) 30 | }) 31 | } 32 | 33 | function isDevice (transport) { 34 | const scope = 'device' // local device only 35 | 36 | return ( 37 | transport.scope === scope || 38 | transport.scope.includes(scope) 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /util/incoming-connections.js: -------------------------------------------------------------------------------- 1 | const get = require('lodash.get') 2 | const os = require('os') 3 | const defaultPorts = require('../default-ports') 4 | const ip = require('ip') 5 | 6 | // generates all possible incoming connection settings that you could bind to 7 | module.exports = function (config) { 8 | var incoming 9 | 10 | // We have to deal with some legacy behavior where: 11 | // 12 | // - net port is defined by `config.port` 13 | // - ws port is defined by `config.ws.port` 14 | // - other services have no canonical port config location (TODO?) 15 | const getPort = (service) => { 16 | const defaultPort = defaultPorts[service] 17 | 18 | if (service === 'net') { 19 | return get(config, 'port', defaultPort) 20 | } 21 | if (service === 'ws') { 22 | return get(config, 'ws.port', defaultPort) 23 | } 24 | 25 | return defaultPort 26 | } 27 | 28 | 29 | //legacy configuration didn't have a scopes concept, 30 | //so interpret that as every scope at once. 31 | //I think there is probably a better way to do this, 32 | //but am fairly sure this will probably work. 33 | const allScopes = ['device', 'local', 'public'] 34 | 35 | // If `config.host` is defined then we don't need to enumerate interfaces. 36 | if (config.host) { 37 | incoming = { 38 | net: [{ 39 | host: config.host, 40 | port: getPort('net'), 41 | scope: allScopes, 42 | transform: 'shs' 43 | }], 44 | ws: [{ 45 | host: config.host, 46 | port: getPort('ws'), 47 | scope: allScopes, 48 | transform: 'shs' 49 | }] 50 | } 51 | } else { 52 | // Trying to hardcode reasonable defaults here doesn't seem possible. 53 | // 54 | // Instead, the below code enumerates all network interfaces and adds them 55 | // to `config.connections.incoming` for each service in `defaultPorts`. 56 | 57 | // If you aren't familiar, you should at least skim these docs: 58 | // https://nodejs.org/api/os.html#os_os_networkinterfaces 59 | const interfaces = os.networkInterfaces() 60 | 61 | // Game plan: we're going to enumerate the services (e.g. net and ws) and 62 | // return an object that looks like this: 63 | // 64 | // { 65 | // net: [ interface, interface, ... ] 66 | // ws: [ interface, interface, ... ] 67 | // } 68 | incoming = Object.keys(defaultPorts).map((service) => { 69 | return { 70 | service, 71 | interfaces: Object.values(interfaces).reduce((acc, val) => { 72 | // Future TODO: replace with shiny new `Array.prototype.flat()`. 73 | return acc.concat(val) 74 | }, []).filter(item => { 75 | // We want to avoid scoped IPv6 addresses since they don't seem to 76 | // play nicely with the Node.js networking stack. These addresses 77 | // often start with `fe80` and throw EINVAL when we try to bind to 78 | // them. 79 | return item.scopeid == null || item.scopeid === 0 80 | }).map(item => { 81 | // This bit is simple because the ssb-config options for `incoming` 82 | // can either be hardcoded or directly inferred from `interfaces`. 83 | 84 | //if an interface is internal, it can only be accessed from the device. 85 | //if it's got a private ip address it can only be accessed from some network. 86 | //otherwise, it's presumably a publically accessable address. 87 | var scope = ( 88 | item.internal ? 'device' 89 | : ip.isPrivate(item.address) ? 'local' 90 | : 'public' 91 | ) 92 | 93 | return { 94 | host: item.address, 95 | port: getPort(service), 96 | scope: [scope], 97 | transform: 'shs' 98 | } 99 | }) 100 | } 101 | }).reduce((result, obj) => { 102 | // This `reduce()` step is necessary because we need to return an object 103 | // rather than an array. There may be a simpler way to do this. 104 | result[obj.service] = obj.interfaces 105 | return result 106 | }, {}) 107 | } 108 | 109 | incoming.unix = [{ "scope":"device", "transform":"noauth" }] 110 | 111 | return incoming 112 | } 113 | --------------------------------------------------------------------------------