Added rewrite of cyphernodeconf

This commit is contained in:
SKP
2019-06-01 20:43:57 +02:00
committed by kexkey
parent 7818b25256
commit 226c32794c
74 changed files with 12816 additions and 36 deletions

View File

@@ -32,7 +32,7 @@ build_docker_images() {
trace "Updating SatoshiPortal repos"
trace "Creating cyphernodeconf image"
docker build install/ -t cyphernode/cyphernodeconf:$CONF_VERSION
docker build cyphernodeconf_docker/ -t cyphernode/cyphernodeconf:$CONF_VERSION
trace "Creating cyphernode images"
docker build api_auth_docker/ -t cyphernode/gatekeeper:$GATEKEEPER_VERSION \

View File

@@ -0,0 +1,12 @@
node_modules
Dockerfile
.dockerignore
.eslintignore
.eslintrc.json
.gitignore
jest.config.js
LICENSE
build.sh
run.sh
jest.config.js
test

View File

@@ -0,0 +1,4 @@
usr
scripts
config
node_modules

View File

@@ -0,0 +1,67 @@
{
"env": {
"es6": true,
"node": true,
"mocha": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"rules": {
"padding-line-between-statements": [
"error",
{
"blankLine": "always",
"prev": "function",
"next": "*"
}
],
"newline-per-chained-call": "error",
"no-tabs": "error",
"no-nested-ternary": "error",
"no-trailing-spaces": "error",
"no-multiple-empty-lines": "error",
"no-whitespace-before-property": "error",
"new-cap": "error",
"lines-around-comment": "error",
"key-spacing": "error",
"comma-style": "error",
"brace-style": "error",
"camelcase": "error",
"handle-callback-err": "error",
"no-new-require": "warn",
"no-sync": "warn",
"no-mixed-requires": "error",
"global-require": "error",
"comma-spacing": "error",
"eqeqeq": "error",
"curly": "error",
"keyword-spacing": "error",
"object-curly-spacing": [
"warn",
"always"
],
"object-curly-newline": [
"warn",
"always"
],
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

8
cyphernodeconf_docker/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*~
*.log
*.sw[a-z]
DEADJOE
node_modules
coverage
.DS_Store
.idea

View File

@@ -0,0 +1,16 @@
FROM node:12.2.0-alpine
ENV EDITOR=/usr/bin/nano
COPY . /app
WORKDIR /app
RUN mkdir /data && \
apk add --update su-exec p7zip openssl nano apache2-utils && \
rm -rf /var/cache/apk/* && \
npm ci --production
WORKDIR /app
ENTRYPOINT ["/sbin/su-exec"]

5
cyphernodeconf_docker/build.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
VERSION=v0.2.0-test
docker build . -t cyphernode/cyphernodeconf:${VERSION}

View File

@@ -0,0 +1,10 @@
[
{
"name": "Lightning node",
"value": "lightning"
},
{
"name": "Opentimestamps client",
"value": "otsclient"
}
]

View File

@@ -0,0 +1,45 @@
{
"features": "What optional <font underline='true'>features</font> do you want me to activate?",
"net": "Which Bitcoin <font underline='true'>network</font> do you want Cyphernode to run on?",
"run_as_different_user": "I recommend running Cyphernode as a <font underline='true'>different user</font> when possible. Using your current user would give Cyphernode your current access rights, which could be a security issue especially if you are a sudoer. Please note that this feature is not supported on OSX at runtime, but you will be fine activating it in case you want to use the configuration file on another machine.",
"username": "Run Cyphernode as <font underline='true'>what user</font>? I recommend user <font color='# 0000ff'>cyphernode</font>. If the user does not exist, I will create it for you.",
"use_xpub": "Cyphernode can derive Bitcoin addresses from an xPub and the derivation path you want. If you want, you can provide your xPub and derivation path right now and call 'derive' with only the index instead of having to pass your xPub and derivation path on each call.",
"xpub": "Cyphernode can derive addresses from your <font underline='true'>default xPub key</font>. With that functionality, you don't have to provide your xPub every time you call the derivation endpoints.",
"derivation_path": "Cyphernode can derive addresses from your <font underline='true'>default derivation path</font>. With that functionality, you don't have to provide your derivation path every time you call the derivation endpoints.",
"proxy_datapath": "The Cyphernode proxy container, which routes all the requests to the right services uses a sqlite3 database to keep track of some things. This DB will be mounted from a <font underline='true'>local path</font>, easy to back up from outside Docker. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"proxy_datapath_custom": " ",
"gatekeeper_clientkeyspassword": "The Gatekeeper checks all the incoming requests for the right permissions before delegating them to the proxy. Following the JWT standard, it uses HMAC signature verification to allow or deny access. Signatures are created and verified using secret keys. I am going to generate the secret keys and keep them in an encrypted file. You will be able to download this encrypted file later. Please provide the <font underline='true'>encryption passphrase</font>.",
"gatekeeper_clientkeyspassword_c": " ",
"gatekeeper_recreatekeys": "The Gatekeeper keys already exist, do you want to <font underline='true'>regenerate</font> them? This will overwrite existing ones.",
"gatekeeper_recreatecert": "The Gatekeeper TLS (SSL) certificates already exist, do you want to <font underline='true'>regenerate</font> them? This will overwrite existing ones.",
"gatekeeper_datapath": "The Gatekeeper's files (TLS certs, HMAC keys, Groups/API) will be stored in a container's mounted directory. Please provide the <font underline='true'>local mounted path</font> to that directory. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"gatekeeper_datapath_custom": "Provide the <font underline='true'>full path name</font> where the Gatekeeper's files will be saved.",
"gatekeeper_edit_apiproperties": "If you know what you are doing, it is possible to manually edit the API endpoints/groups authorization. (Not recommended)",
"gatekeeper_apiproperties": "You are about to edit the api.properties file. The format of the file is pretty simple: for each action, you will find what access group can access it. <font color='# 0000ff'>Admin</font> group can do what <font color='# 0000ff'>Spender</font> group can, and <font color='# 0000ff'>Spender</font> group can do what <font color='# 0000ff'>Watcher</font> group can. <font color='# 0000ff'>Internal</font> group is for the endpoints accessible only within the Docker network, like the backoffice tasks used by the Cron container. The access groups for each API id/key are found in the <font color='# 0000ff'>keys.properties</font> file.",
"gatekeeper_cns": "I use <font underline='true'>domain names</font> and/or <font underline='true'>IP addresses</font> to create valid TLS certificates. For example, if <font color='# 0000ff'>https://cyphernodehost/getbestblockhash</font> and <font color='# 0000ff'>https://192.168.7.44/getbestblockhash</font> will be used, enter <font color='# 0000ff'>cyphernodehost, 192.168.7.44</font> as a possible domains. <font color='# 0000ff'>127.0.0.1, localhost, gatekeeper</font> will be automatically added to your list. Make sure the provided domain names are in your DNS or client's hosts file and is reachable.",
"traefik_datapath": "The Traefik's files will be stored in a container's mounted directory. Please provide the <font underline='true'>local mounted path</font> to that directory. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"traefik_datapath_custom": "Provide the <font underline='true'>full path name</font> where the Traefik's files will be saved.",
"bitcoin_mode": "Cyphernode will spawn a new <font underline='true'>Bitcoin Core</font> full node for its own use. If you already have Bitcoin Core node data, you can use the directory containing that data directly or copy the contents of it to a new directory to be used by cyphernode. Be aware that the files might change ownership, if you run cyphernode as a different user. In case you want to move the blockchain data to another node you might need to change the owner to fit the configuration of that node.",
"bitcoin_node_ip": "Cyphernode uses <font color='#00ff00'>Bitcoin Core</font> RPC interface for its tasks. Please provide the <font underline='true'>IP address</font> of your current Bitcoin Core node.",
"bitcoin_rpcuser": "Bitcoin Core's <font underline='true'>RPC username</font> used by Cyphernode when calling the node.",
"bitcoin_rpcpassword": "Bitcoin Core's <font underline='true'>RPC password</font> used by Cyphernode when calling the node.",
"bitcoin_prune": "If you don't have at least 350GB of disk space, you should run Bitcoin Core in <font underline='true'>prune mode</font>. <font color='#00ff00'>NOTE</font>: when running Bitcoin Core in prune mode, the incoming transactions' fees cannot be computed by Cyphernode and won't be part of the addresses watching's callbacks payload.",
"bitcoin_prune_size": "Minimum <font underline='true'>size</font> is <font color='#00ff00'>550</font>. This option specifies the maximum number in MB Bitcoin Core will allocate for raw block & undo data.",
"bitcoin_uacomment": "<font underline='true'>User Agent</font> string used by Bitcoin Core. (Optional)",
"bitcoin_datapath": "<font underline='true'>Path name</font> to where Bitcoin Core's data files (blockchain data, wallets, configs, etc.) are stored. This directory will be mounted into the Bitcoin node's container. If you already have a sync'ed node, you can copy data there to be used by the node, instead of resyncing everything. <font color='#00ff00'>NOTE</font>: only copy chainstate/ and blocks/ contents. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"bitcoin_datapath_custom": " ",
"bitcoin_expose": "By default, Bitcoin node ports (RPC and protocol) won't be <font underline='true'>published</font> outside of Docker. Do you want to expose them so that your node can be accessed from outside of the Docker network?",
"lightning_implementation": "Multiple <font underline='true'>LN implementations</font> exist. Please choose the one you want to use with Cyphernode.",
"lightning_external_ip": "If you want you LN node to be accessible from the Internet, provide the <font underline='true'>IP address</font> that other LN nodes will use to connect to it. This is usually your router's public IP. <font color='#00ff00'>NOTE</font>: In case you are running Cyphernode at home. This option won't make your router forward needed LN ports; you still need to configure and manage that part yourself in your router configuration.",
"lightning_nodename": "LN nodes have names. Choose the <font underline='true'>name you want</font> for yours.",
"lightning_nodecolor": "LN nodes have colors. Choose the <font underline='true'>color you want</font> for yours in RGB format (RRGGBB). For example, pure red would be <font color='#ff0000'>ff0000</font>.",
"lightning_datapath": "<font underline='true'>Path name</font> to where LN's data files are stored. This directory will be mounted into the LN node's container. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"lightning_datapath_custom": " ",
"lightning_expose": "By default, LN node port will be <font underline='true'>published</font> outside of Docker. Do you want to hide it so that your node can't be accessed from outside of the Docker network?",
"otsclient_datapath": "<font underline='true'>Full path</font> where the OTS files will be stored. This path will be mounted into the otsclient container which will create the OTS files when <font color='#00ff00'>stamping</font> and update them when <font color='#00ff00'>upgrading</font> stamps. It will also be mounted to the proxy container so that it can serve the <font color='#00ff00'>ots_getfile</font> and send the OTS files to clients. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"otsclient_datapath_custom": " ",
"installer_mode": "Only one <font underline='true'>installation mode</font> is supported, right now: <font color='#0000ff'>local docker (self-hosted)</font>. Choose wisely ;-)",
"installer_cleanup": "Do you want to <font underline='true'>remove</font> this configurator Docker image after installation? This would free about 150MB of disk space.",
"docker_mode": "Cyphernode Docker services can be run using <font underline='true'>Docker Swarm</font> (https://docs.docker.com/engine/swarm/) or <font underline='true'>docker-compose</font> (https://docs.docker.com/compose/overview/). Both will work, some users prefer one to another depending on deployment types, scalability, current framework, etc.",
"__default__": ""
}

View File

@@ -0,0 +1,12 @@
const App = require( './lib/app.js' );
const main = async ( args ) => {
const app = new App();
const noWizard = args.indexOf('recreate') !== -1;
await app.start( {
noWizard: noWizard,
noSplashScreen: noWizard
} );
};
main( process.argv.slice( 2, process.argv.length ) );

View File

@@ -0,0 +1,185 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// Respect "browser" field in package.json when resolving modules
// browser: false,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/fb/q84grm8d0w3gz0y0yx_bvx700000gn/T/jest_dx",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: null,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: [
//"json",
"text",
//"lcov",
//"clover"
],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: null,
// A path to a custom dependency extractor
// dependencyExtractor: null,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files usin a array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: null,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: null,
// A set of global variables that need to be available in all test environments
// globals: {},
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "json",
// "jsx",
// "ts",
// "tsx",
// "node"
// ],
// A map from regular expressions to module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: null,
// Run tests from one or more projects
// projects: null,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: null,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: null,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: null,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: null,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: null,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};

View File

@@ -0,0 +1,4 @@
module.exports = {
reset: '\u001B8\u001B[u',
clear: '\u001Bc'
};

View File

@@ -0,0 +1,83 @@
const spawn = require('child_process').spawn;
const configEntryRegexp = /^kapi_id="(\w+)";kapi_key="(\w+)";kapi_groups="(.+)";(.+)$/;
module.exports = class ApiKey {
constructor( id, groups, key, script ) {
this.setId(id || '000');
this.setGroups(groups || ['stats'] );
this.setScript(script || 'eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}' );
this.setKey(key);
}
setFromConfigEntry(configEntry ) {
const match = configEntryRegexp.exec( configEntry );
if( match ) {
this.setId( match[1] );
this.setKey( match[2] );
this.setGroups( match[3].split(',').map( (e)=>e.trim() ) )
this.setScript( match[4] );
}
}
setGroups( groups ) {
this.groups = groups;
}
setId( id ) {
this.id = id;
}
setScript( script ) {
this.script = script;
}
setKey( key ) {
this.key = key;
}
async randomiseKey() {
try {
//const dd = spawn('/bin/dd if=/dev/urandom bs=32 count=1 | /usr/bin/xxd -pc 32');
const dd = spawn("dd if=/dev/urandom bs=32 count=1 | xxd -pc32", [], {stdio: ['ignore', 'pipe', 'ignore' ], shell: true} );
const result = await new Promise( function(resolve, reject ) {
let result = '';
dd.stdout.on('data', function( a,b,c) {
let chunk = a.toString().trim();
result += chunk;
});
dd.stdout.on('end', function() {
result = result.replace(/[^a-zA-Z0-9]/,'');
resolve(result);
});
dd.stdout.on('error', function(err) {
reject(err);
})
});
this.key = result;
} catch( err ) {
reject(err);
}
}
getKey() {
return this.key;
}
getConfigEntry() {
if( !this.key ) {
return;
}
return `kapi_id="${this.id}";kapi_key="${this.key}";kapi_groups="${this.groups.join(',')}";${this.script}`;
}
getClientInformation() {
return `${this.id}=${this.key}`;
}
};

View File

@@ -0,0 +1,515 @@
const {promisify} = require('util');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const wrap = require('wrap-ansi');
const validator = require('validator');
const coinstring = require('coinstring');
const inquirer = require('inquirer');
const colorsys = require('colorsys');
const ejs = require( 'ejs' );
const ejsRenderFileAsync = promisify( ejs.renderFile ).bind( ejs );
const html2ansi = require('./html2ansi.js');
const name = require('./name.js');
const Archive = require('./archive.js');
const ApiKey = require('./apikey.js');
const Cert = require('./cert.js');
const htpasswd = require( './htpasswd.js');
const Config = require('./config.js');
const SplashScreen = require( './splashScreen.js' );
const ansi = require( './ansi.js' );
const features = require('../features.json');
const uaCommentRegexp = /^[a-zA-Z0-9 \.,:_\-\?\/@]+$/; // TODO: look for spec of unsafe chars
const userRegexp = /^[a-zA-Z0-9\._\-]+$/;
const maxWidth = 82;
const keyIds = {
'000': ['stats'],
'001': ['stats','watcher'],
'002': ['stats','watcher','spender'],
'003': ['stats','watcher','spender','admin']
};
const configArchiveFileName = 'config.7z';
const keyArchiveFileName = 'client.7z';
const prefix = () => {
return chalk.green('Cyphernode')+': ';
};
const randomColor = () => {
const hex = colorsys.hslToHex( { h: (Math.random()*360)<<0, s: 50, l: 50 } );
return hex.substr(1);
};
let prompters = [];
fs.readdirSync(path.join(__dirname, '..','prompters')).forEach((file) => {
prompters.push(require(path.join(__dirname, '..','prompters',file)));
});
module.exports = class App {
constructor() {
this.features = features;
if( fs.existsSync(path.join('/data', 'exitStatus.sh')) ) {
fs.unlinkSync(path.join('/data', 'exitStatus.sh'));
}
this.splash = new SplashScreen( {
frameDir: path.join(__dirname, '..', 'splash' ),
enableFortune: false,
fortuneChalk: chalk.default.bold,
width: maxWidth,
fortuneSpacing: 3
} );
}
async start( options ) {
options = options || {};
this.sessionData = {
defaultDataDirBase: process.env.DEFAULT_DATADIR_BASE || process.env.HOME,
setupDir: process.env.SETUP_DIR || path.join( process.env.HOME, 'cyphernode' ),
default_username: process.env.DEFAULT_USER || '',
gatekeeper_version: process.env.GATEKEEPER_VERSION,
gatekeeper_cns: process.env.DEFAULT_CERT_HOSTNAME,
proxy_version: process.env.PROXY_VERSION,
proxycron_version: process.env.PROXYCRON_VERSION,
pycoin_version: process.env.PYCOIN_VERSION,
otsclient_version: process.env.OTSCLIENT_VERSION,
bitcoin_version: process.env.BITCOIN_VERSION,
lightning_version: process.env.LIGHTNING_VERSION,
notifier_version: process.env.NOTIFIER_VERSION,
setup_version: process.env.SETUP_VERSION,
noWizard: !!options.noWizard,
noSplashScreen: !!options.noSplashScreen,
lightning_nodename: name.generate(),
lightning_nodecolor: randomColor(),
installer_cleanup: false,
devmode: process.env.DEVMODE || false
};
await this.setupConfigArchive();
if( !this.sessionData.noSplashScreen ) {
await this.splash.show();
}
let missingProperties = [];
if( this.config.validateErrors && this.config.validateErrors.length ) {
for( let error of this.config.validateErrors ) {
if( error.keyword === 'required' && error.params && error.params.missingProperty ) {
missingProperties.push( error.params.missingProperty );
}
}
}
if( this.sessionData.noWizard && missingProperties.length && this.config.isLoaded ) {
console.log(chalk.bold.red('Unable to migrate client.7z non-interactively. Rerun without the -r option') );
process.exit(1);
}
if( !this.sessionData.noWizard ) {
// save gatekeeper key password to check if it changed
this.sessionData.gatekeeper_clientkeyspassword = this.config.data.gatekeeper_clientkeyspassword;
if( missingProperties.length && this.config.isLoaded ) {
this.sessionData.markProperties = missingProperties;
}
await this.startWizard();
}
await this.processProps();
await this.writeFiles();
}
async setupConfigArchive() {
this.config = new Config( {
setup_version: this.sessionData.setup_version,
docker_versions: {
'gatekeeper': this.sessionData.gatekeeper_version,
'proxy': this.sessionData.proxy_version,
'proxycron': this.sessionData.proxycron_version,
'pycoin': this.sessionData.pycoin_version,
'otsclient': this.sessionData.otsclient_version,
'bitcoincore': this.sessionData.bitcoin_version,
'clightning': this.sessionData.lightning_version,
'notifier': this.sessionData.notifier_version
}
} );
if( !fs.existsSync(this.destinationPath(configArchiveFileName)) ) {
let r = {};
process.stdout.write(ansi.clear+ansi.reset);
while( !r.password0 || !r.password1 || r.password0 !== r.password1 ) {
if( r.password0 && r.password1 && r.password0 !== r.password1 ) {
console.log(chalk.bold.red('Passwords do not match')+'\n');
}
r = await this.prompt([{
type: 'password',
name: 'password0',
message: prefix()+chalk.bold.blue('Choose your configuration password'),
filter: this.trimFilter
},
{
type: 'password',
name: 'password1',
message: prefix()+chalk.bold.blue('Confirm your configuration password'),
filter: this.trimFilter
}]);
}
this.sessionData.configurationPassword = r.password0;
} else {
try {
let r = {};
if( process.env.CFG_PASSWORD ) {
this.sessionData.configurationPassword = process.env.CFG_PASSWORD;
} else {
process.stdout.write(ansi.reset);
while( !r.password ) {
r = await this.prompt([{
type: 'password',
name: 'password',
message: prefix()+chalk.bold.blue('Enter your configuration password?'),
filter: this.trimFilter
}]);
}
this.sessionData.configurationPassword = r.password;
}
try {
await this.config.deserialize(
this.destinationPath(configArchiveFileName),
this.sessionData.configurationPassword,
);
// store clientkeyspassword in sessionData so it can be retrieved by getDefault
// and a simple return will not result in a password mismatch
if( this.config.data.hasOwnProperty('gatekeeper_clientkeyspassword') ) {
this.sessionData.gatekeeper_clientkeyspassword_c =
this.config.data.gatekeeper_clientkeyspassword;
}
} catch (e) {
console.log(chalk.bold.red(e));
process.exit();
}
} catch( err ) {
console.log(chalk.bold.red('config archive is corrupt.'));
process.exit(1);
}
}
this.config.data.adminhash = await htpasswd(this.sessionData.configurationPassword);
for( let c of this.features ) {
c.checked = this.isChecked( 'features', c.value );
}
}
async startWizard() {
let r = await this.prompt([{
type: 'confirm',
name: 'enablehelp',
message: prefix()+'Enable help?',
default: this.getDefault( 'enablehelp' ),
}]);
this.config.data.enablehelp = r.enablehelp;
if( this.config.data.enablehelp ) {
this.help = require('../help.json');
}
let prompts = [];
for( let m of prompters ) {
let newPrompts = m.prompts(this);
if( this.sessionData.markProperties &&
this.sessionData.markProperties.length &&
this.config.isLoaded ) {
for( let prompt of newPrompts ) {
if( this.sessionData.markProperties.indexOf(prompt.name) !== -1 ) {
prompt.message = prompt.message+' '+chalk.bgGreen('new option');
}
}
}
prompts = prompts.concat(newPrompts);
}
const props = await this.prompt(prompts);
this.config.data = Object.assign(this.config.data, props);
}
async processProps() {
// creates keys if they don't exist or we say so.
if( this.config.data.gatekeeper_recreatekeys ||
this.config.data.gatekeeper_keys.configEntries.length===0 ) {
delete this.config.data.gatekeeper_recreatekeys;
let configEntries = [];
let clientInformation = [];
for( let keyId in keyIds ) {
const apikey = await this.createRandomKey( keyId, keyIds[keyId] );
configEntries.push(apikey.getConfigEntry());
clientInformation.push(apikey.getClientInformation());
}
this.config.data.gatekeeper_keys = {
configEntries: configEntries,
clientInformation: clientInformation
}
}
const cert = new Cert();
this.sessionData.cns = cert.cns(this.config.data.gatekeeper_cns);
// create certs if they don't exist or we say so.
if( this.config.data.gatekeeper_recreatecert ||
!this.config.data.gatekeeper_sslcert ||
!this.config.data.gatekeeper_sslkey ) {
delete this.config.data.gatekeeper_recreatecert;
const cert = new Cert();
console.log(chalk.bold.green( '☕ Generating gatekeeper cert. This may take a while ☕' ));
try {
const result = await cert.create(this.sessionData.cns);
if( result.code === 0 ) {
this.config.data.gatekeeper_sslkey = result.key.toString();
this.config.data.gatekeeper_sslcert = result.cert.toString();
} else {
console.log(chalk.bold.red( 'error! Gatekeeper cert was not created' ));
}
} catch( err ) {
console.log(chalk.bold.red( 'error! Gatekeeper cert was not created' ));
}
}
}
async createRandomKey( id, groups ) {
if( !id || !groups || !groups.length ) {
return;
}
const apikey = new ApiKey();
apikey.setId(id);
apikey.setGroups(groups);
await apikey.randomiseKey();
return apikey
}
async writeFiles() {
console.log( chalk.green( ' create' )+' '+configArchiveFileName );
if( !this.config.serialize(
this.destinationPath(configArchiveFileName),
this.sessionData.configurationPassword
) ) {
console.log(chalk.bold.red( 'error! Config archive was not written' ));
}
const pathProps = [
'gatekeeper_datapath',
'traefik_datapath',
'proxy_datapath',
'bitcoin_datapath',
'lightning_datapath',
'otsclient_datapath'
];
for( let pathProp of pathProps ) {
if( this.config.data[pathProp] === '_custom' ) {
this.config.data[pathProp] = this.config.data[pathProp+'_custom'] || '';
}
}
for( let m of prompters ) {
const name = m.name();
for( let t of m.templates(this.config.data) ) {
const p = path.join(name,t);
const destFile = this.destinationPath(p);
const targetDir = path.dirname( destFile );
if( !fs.existsSync(targetDir) ) {
fs.mkdirSync(targetDir, { recursive: true });
}
const result = await ejsRenderFileAsync( this.templatePath(p), Object.assign({},this.config.data,this.sessionData), {} );
console.log( chalk.green( ' create' )+' '+p );
fs.writeFileSync( destFile, result );
}
}
console.log( chalk.green( ' create' )+' '+keyArchiveFileName );
if( this.config.data.gatekeeper_keys && this.config.data.gatekeeper_keys.clientInformation ) {
if( this.sessionData.gatekeeper_clientkeyspassword !== this.config.data.gatekeeper_clientkeyspassword &&
fs.existsSync(this.destinationPath(keyArchiveFileName)) ) {
fs.unlinkSync( this.destinationPath(keyArchiveFileName) );
}
const archive = new Archive( this.destinationPath(keyArchiveFileName), this.config.data.gatekeeper_clientkeyspassword );
if( !await archive.writeEntry( 'keys.txt', this.config.data.gatekeeper_keys.clientInformation.join('\n') ) ) {
console.log(chalk.bold.red( 'error! Client gatekeeper key archive was not written' ));
}
if( !await archive.writeEntry( 'cacert.pem', this.config.data.gatekeeper_sslcert ) ) {
console.log(chalk.bold.red( 'error! Client gatekeeper key archive was not written' ));
}
}
fs.writeFileSync(path.join('/data', 'exitStatus.sh'), 'EXIT_STATUS=0');
}
async prompt( questions ) {
if( !questions ) {
return {};
}
const r = await inquirer.prompt( questions );
return r;
}
/* some utils */
destinationPath( relPath ) {
return path.join( '/data', relPath );
}
templatePath( relPath ) {
return path.join(__dirname, '..','templates',relPath );
}
isChecked(name, value ) {
return this.config.data && this.config.data[name] && this.config.data[name].indexOf(value) != -1 ;
}
getDefault(name) {
if( this.config && this.config.data && this.config.data.hasOwnProperty(name) ) {
return this.config.data[name];
}
if( this.sessionData && this.sessionData.hasOwnProperty(name) ) {
return this.sessionData[name];
}
}
optional(input, validator) {
if( input === undefined ||
input === null ||
input === '' ) {
return true;
}
return validator(input);
}
ipOrFQDNValidator(host ) {
host = (host+"").trim();
if( !(validator.isIP(host) ||
validator.isFQDN(host)) ) {
throw new Error( 'No IP address or fully qualified domain name' )
}
return true;
}
xkeyValidator(xpub ) {
// TOOD: check for version
if( !coinstring.isValid( xpub ) ) {
throw new Error('Not an extended key.');
}
return true;
}
pathValidator(p ) {
return true;
}
derivationPathValidator(path ) {
return true;
}
colorValidator(color) {
if( !validator.isHexadecimal(color) ) {
throw new Error('Not a hex color.');
}
return true;
}
lightningNodeNameValidator(name) {
if( !name || name.length > 32 ) {
throw new Error('Please enter anything shorter than 32 characters');
}
return true;
}
notEmptyValidator(path ) {
if( !path ) {
throw new Error('Please enter something');
}
return true;
}
usernameValidator(user ) {
if( !userRegexp.test( user ) ) {
throw new Error('Choose a valid username');
}
return true;
}
UACommentValidator(comment ) {
if( !uaCommentRegexp.test( comment ) ) {
throw new Error('Unsafe characters in UA comment. Please use only a-z, A-Z, 0-9, SPACE and .,:_?@');
}
return true;
}
trimFilter(input ) {
return (input+"").trim();
}
featureChoices() {
return this.features;
}
setupDir() {
return this.sessionData.setupDir;
}
defaultDataDirBase() {
return this.sessionData.defaultDataDirBase;
}
getHelp(topic ) {
if( !this.config.data.enablehelp || !this.help ) {
return '';
}
const helpText = this.help[topic] || this.help['__default__'];
if( !helpText ||helpText === '' ) {
return '';
}
return "\n\n"+wrap( html2ansi(helpText),maxWidth )+"\n\n";
}
};

View File

@@ -0,0 +1,76 @@
const spawn = require('child_process').spawn;
const stringio = require('@rauschma/stringio');
const defaultArgs = ['-t7z', '-ms=on', '-mhe=on'];
module.exports = class Archive {
constructor( file, password ) {
this.file = file || 'archive.7z'
this.password = password;
}
async readEntry( entryName ) {
if( !entryName ) {
return;
}
let args = defaultArgs.slice();
args.unshift('x');
args.push( '-so' );
if( this.password ) {
args.push('-p'+this.password );
}
args.push( this.file )
args.push( entryName )
const archiver = spawn('7z', args, { stdio: ['ignore', 'pipe', 'ignore'] } );
const result = await stringio.readableToString(archiver.stdout);
try {
await stringio.onExit( archiver );
} catch( err ) {
return { error: err };
}
return { error: null, value: result };
}
async writeEntry( entryName, content ) {
if( !entryName ) {
return;
}
let args = defaultArgs.slice();
args.unshift('a');
if( this.password ) {
args.push('-p'+this.password );
}
args.push( '-si'+entryName );
args.push( this.file )
const archiver = spawn('7z', args, { stdio: ['pipe', 'ignore', 'ignore' ] } );
await stringio.streamWrite(archiver.stdin, content);
await stringio.streamEnd(archiver.stdin);
try {
await stringio.onExit( archiver );
} catch( err ) {
return false;
}
return true;
}
async deleteEntry( entryName ) {
if( !entryName ) {
return;
}
let args = defaultArgs.slice();
args.unshift('d');
if( this.password ) {
args.push('-p'+this.password );
}
args.push( this.file )
args.push( entryName )
const archiver = spawn('7z', args, { stdio: ['ignore', 'pipe','ignore'] } );
try {
await stringio.onExit( archiver );
} catch( err ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,125 @@
const fs = require('fs');
const spawn = require('child_process').spawn;
const defaultArgs = ['req', '-x509', '-newkey', 'rsa:4096', '-nodes'];
const path = require('path');
const tmp = require('tmp');
const validator = require('validator');
const confTmpl = `
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no
[req_distinguished_name]
CN = %PRIMARY_CN%
[v3_ca]
subjectAltName = @alt_names
[alt_names]
%ALT_DOMAINS%
%ALT_IPS%
`;
const domainTmpl = 'DNS.%#% = %DOMAIN%';
const ipTmpl = 'IP.%#% = %IP%';
const standardCNs = ['127.0.0.1','localhost','gatekeeper'];
module.exports = class Cert {
constructor( options ) {
options = options || {};
this.args = options.args || { days: 3650 };
}
cns( cnsString ) {
if( !cnsString ) {
return standardCNs;
}
const cns = cnsString.split(',').map(e=>e.trim().toLowerCase()).filter(e=>!!e);
if( cns.length ) {
return standardCNs.concat(cns);
}
return standardCNs;
}
buildConfig( cns ) {
let ips = [];
let domains = [];
for( let cn of cns ) {
if( validator.isIP(cn) ) {
ips.push( cn );
} else {
domains.push( cn );
}
}
let conf = confTmpl;
if( !domains.length ) {
domains.push('localhost');
}
conf = conf.replace( '%PRIMARY_CN%', domains[0] )
let domainCount = 0;
domains = domains.map( d => domainTmpl.replace( '%#%', ++domainCount ).replace('%DOMAIN%', d) );
conf = conf.replace( '%ALT_DOMAINS%', domains.join('\n') || '' )
let ipCount = 0;
ips = ips.map( ip => ipTmpl.replace( '%#%', ++ipCount ).replace('%IP%', ip) );
conf = conf.replace( '%ALT_IPS%', ips.join('\n') || '' )
return conf;
}
async create( cns ) {
if (!cns || !cns.length) {
throw 'No cns';
}
let args = defaultArgs.slice();
const certFileTmp = tmp.fileSync();
const keyFileTmp = tmp.fileSync();
const confFileTmp = tmp.fileSync();
args.push( '-out' );
args.push( certFileTmp.name );
args.push( '-keyout' );
args.push( keyFileTmp.name );
args.push( '-config' );
args.push( confFileTmp.name );
for( let k in this.args ) {
args.push( '-'+k);
args.push( this.args[k] );
}
const conf = this.buildConfig( cns );
fs.writeFileSync( confFileTmp.name, conf );
let code = await new Promise( function(resolve, reject) {
const openssl = spawn('openssl', args, { stdio: ['ignore', 'ignore', 'ignore'] } );
openssl.on('exit', (code) => {
resolve(code);
});
});
const cert = fs.readFileSync( certFileTmp.name );
const key = fs.readFileSync( keyFileTmp.name );
certFileTmp.removeCallback();
keyFileTmp.removeCallback();
confFileTmp.removeCallback();
return {
code: code,
key: key,
cert: cert
}
}
};

View File

@@ -0,0 +1,186 @@
const Ajv = require('ajv');
const fs = require('fs');
const Archive = require('./archive.js');
const ApiKey = require('./apikey.js');
const name = require('./name.js');
const colorsys = require( 'colorsys');
const latestSchemaVersion='0.2.0';
const defaultSchemaVersion='0.1.0';
const schemas = {
'0.1.0': require('../schema/config-v0.1.0.json'),
'0.2.0': require('../schema/config-v0.2.0.json'),
};
module.exports = class Config {
constructor( options ) {
options = options || {};
this.setup_version = options.setup_version;
this.docker_versions = options.docker_versions;
const ajv = new Ajv({
removeAdditional: true,
useDefaults: true,
coerceTypes: true,
allErrors: true
});
this.validators = {};
for( let v in schemas ) {
this.validators[v]=ajv.compile(schemas[v]);
}
this.migrationPaths = {
'0.1.0|0.2.0': [ this.migrate_0_1_0_to_0_2_0 ]
};
this.setData( { schema_version: latestSchemaVersion } );
this.isLoaded = false;
}
setData( data ) {
if( !data ) {
return;
}
this.data = data;
this.data.schema_version = this.data.schema_version || this.data.__version || defaultSchemaVersion;
this.data.setup_version = this.data.setup_version || this.setup_version;
this.data.docker_versions = this.data.docker_versions || this.docker_versions;
this.validate();
}
async serialize( path, password ) {
this.resolveConfigConflicts();
this.validate();
const configJsonString = JSON.stringify(this.data, null, 4);
const archive = new Archive( path, password );
return await archive.writeEntry( 'config.json', configJsonString );
}
async deserialize( path, password ) {
if( fs.existsSync(path) ) {
const archive = new Archive( path, password );
const r = await archive.readEntry('config.json');
if( r.error ) {
throw( 'Password is wrong. Have a nice day.' );
}
if( !r.value ) {
throw('config archive is corrupt.');
}
this.setData( JSON.parse(r.value) );
this.isLoaded = true;
}
//this.resolveConfigConflicts();
if( this.data.schema_version !== latestSchemaVersion ) {
// migrate here
// create a copy of the old config
fs.copyFileSync( path, path+'-'+this.data.schema_version );
await this.migrate(this.data.schema_version, latestSchemaVersion );
// validate again to strip all illegal properties from config with latest version
this.validate();
}
}
resolveConfigConflicts() {
// TODO solve this in config schema
if( this.data.features && this.data.features.length && this.data.features.indexOf('lightning') !== -1 ) {
this.data.bitcoin_prune = false;
delete this.data.bitcoin_prune_size;
}
}
validate() {
if( !this.data.schema_version ||
!this.validators[this.data.schema_version] ||
Object.keys( schemas ).indexOf( this.data.schema_version ) == -1 ) {
throw "Unknown version in data"
}
// this will assign default values from the schema
this.valid = this.validators[this.data.schema_version]( this.data );
this.validateErrors = this.validators[this.data.schema_version].errors;
}
async migrate(sourceVersion, targetVersion) {
const migrations = this.migrationPaths[sourceVersion+'|'+targetVersion];
if( !migrations ) {
return;
}
for( let migration of migrations ) {
await migration.apply(this);
}
}
async migrate_0_1_0_to_0_2_0() {
if( this.data.schema_version != '0.1.0' ) {
return;
}
this.data.schema_version = '0.2.0';
// rewrite specific properties with incompatible content
// gatekeeper_keys: add stats group to all keys and add a label containing only
// the stats group
const gatekeeper_keys = this.data.gatekeeper_keys;
for( let i=0; i<gatekeeper_keys.configEntries.length; i++ ) {
const apiKey = new ApiKey();
apiKey.setFromConfigEntry(gatekeeper_keys.configEntries[i]);
apiKey.groups.unshift('stats');
gatekeeper_keys.configEntries[i]=apiKey.getConfigEntry();
gatekeeper_keys.clientInformation[i]=apiKey.getClientInformation();
}
const apiKeyStatsOnly = new ApiKey('000',['stats']);
await apiKeyStatsOnly.randomiseKey();
gatekeeper_keys.configEntries.unshift( apiKeyStatsOnly.getConfigEntry() );
gatekeeper_keys.clientInformation.unshift( apiKeyStatsOnly.getClientInformation() );
// remove all empty props to generate proper errors
for( let k in this.data ) {
if( !this.data.hasOwnProperty(k) ) {
continue;
}
if( this.data[k] === '' ) {
delete this.data[k];
}
}
// lightning_nodecolor
if( !this.data.lightning_nodecolor ) {
this.data.lightning_nodecolor =
colorsys.hslToHex( { h: (Math.random()*360)<<0, s: 50, l: 50 } ).substr(1);
}
// lightning_nodename
if( !this.data.lightning_nodename ) {
this.data.lightning_nodename = name.generate();
}
// xpub && use_xpub
if( !this.data.xpub ) {
this.data.use_xpub = false;
}
}
};

View File

@@ -0,0 +1,46 @@
const parse5 = require('parse5');
const chalk = require('chalk');
const options = {
scriptingEnabled: false
}
const convert = function(data){
// recursively flatten
let v = data.childNodes && data.childNodes.length?
data.childNodes.map(d=> convert(d)).join(''):
data.value?data.value:'';
switch(data.tagName){
case 'br':
v += '\n'
break
case 'font':
if( data.attrs && data.attrs.length ) {
for( let attr of data.attrs ) {
if( attr.name === 'color' && /^#[a-f0-9]{6}$/.test(attr.value) ) {
v = chalk.hex(attr.value)(v);
}
if( attr.name === 'bold' && attr.value === 'true' ) {
v = chalk.bold(v);
}
if( attr.name === 'italic' && attr.value === 'true' ) {
v = chalk.italic(v);
}
if( attr.name === 'underline' && attr.value === 'true' ) {
v = chalk.underline(v);
}
if( attr.name === 'strikethrough' && attr.value === 'true' ) {
v = chalk.strikethrough(v);
}
}
}
break;
}
return v;
}
module.exports = function(html){
return convert(parse5.parseFragment(html, options));
}

View File

@@ -0,0 +1,23 @@
const exec = require('child_process').exec;
module.exports = async ( password ) => {
if( !password ) {
return null;
}
password = password.replace(/'/g, `'\\''`);
return await new Promise( (resolve) => {
exec('htpasswd -bnB admin \''+password+'\' | cut -sd \':\' -f2', (error, stdout, stderr) => {
if (error) {
return resolve(null);
}
// remove newline at the end
resolve(stdout.substr(0,stdout.length-1));
});
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
const fs = require('fs');
const path = require('path');
const ansi = require( './ansi.js' );
const lunicode = require('lunicode');
const easeOutCubic = (t, b, c, d) => {
return c*((t=t/d-1)*t*t+1)+b;
};
lunicode.tools.creepify.options.top = true; // add diacritics on top. Default: true
lunicode.tools.creepify.options.middle = true; // add diacritics in the middle. Default: true
lunicode.tools.creepify.options.bottom = true; // add diacritics on the bottom. Default: true
lunicode.tools.creepify.options.maxHeight = 15; // How many diacritic marks shall we put on top/bottom? Default: 15
lunicode.tools.creepify.options.randomization = 100; // 0-100%. maxHeight 100 and randomization 20%: the height goes from 80 to 100. randomization 70%: height goes from 30 to 100. Default: 100
const fortunes = [
'Cause fuck central banking',
'Not your keys, not your bitcoin',
'Don\'t trust, verify',
'Craig Wright is a fraud',
'HODL!'
];
module.exports = class SplashScreen {
constructor( options ) {
options = options || {};
if( !options.frameDir ) {
throw "no frame directory to load"
}
this.width = options.width || 82;
this.fortuneEnabled = !!options.enableFortune;
this.fortuneSpacing = options.fortuneSpacing || 0;
this.fortuneChalk = options.fortuneChalk;
this.loadFramesFromDir( options.frameDir );
if( this.fortuneEnabled ) {
let fortune = this.fortune();
if( fortune.length > this.width-2 ) {
fortune = fortune.substr(0,this.width-2);
}
fortune = this.center(fortune);
let fortuneLines = [];
fortuneLines.push( this.creepify(fortune) )+'\n';
for( let i=0; i<this.frames.length; i++ ) {
for( let j=0; j<fortuneLines.length; j++ ) {
this.frames[i] += fortuneLines[j];
}
}
}
}
loadFramesFromDir( frameDir ) {
this.frames = [];
fs.readdirSync(frameDir).forEach((file) => {
this.frames.push(fs.readFileSync(path.join(__dirname,'..','splash',file)));
});
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
fortune() {
return fortunes[ Math.random()*fortunes.length << 0 ];
}
creepify( string ) {
if( this.fortuneChalk ) {
return this.fortuneChalk(lunicode.tools.creepify.encode( string ));
}
return lunicode.tools.creepify.encode( string );
}
center( string ) {
const offset = ((this.width - string.length)*0.5) << 0;
for( let i=0; i<offset; i++ ) {
string = ' '+string+' ';
}
return string;
}
async show() {
const frame0 = this.frames[0];
const frame0lines = frame0.toString().split('\n');
const frame0lineCount = frame0lines.length;
const steps = 10;
await this.sleep(250);
process.stdout.write(ansi.clear);
await this.sleep(150);
for( let i=0; i<=steps; i++ ) {
const pos = easeOutCubic( i, 0, frame0lineCount, steps ) | 0;
process.stdout.write(ansi.reset);
for( let l=frame0lineCount-pos; l<frame0lineCount; l++ ) {
process.stdout.write( frame0lines[l]+'\n' );
}
await this.sleep(33);
}
if( this.frames.length > 1 ) {
await this.sleep(400);
for( let frame of this.frames ) {
process.stdout.write(ansi.reset);
process.stdout.write(frame.toString());
await this.sleep(33);
}
}
await this.sleep(400);
process.stdout.write('\n');
}
};

5486
cyphernodeconf_docker/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
{
"name": "cyphernodeconf",
"version": "0.0.0",
"description": "",
"homepage": "",
"scripts": {
"test": "jest",
"coverageHtml": "jest --coverage --coverageReporters html",
"lint": "./node_modules/.bin/eslint lib/**/*.js",
"lintfix": "./node_modules/.bin/eslint lib/**/**.js --fix"
},
"author": "jash <jash@schulterklopfer-productions.de>",
"main": "lib/index.js",
"keywords": [
"cyphernode"
],
"dependencies": {
"@rauschma/stringio": "^1.4.0",
"ajv": "^6.10.0",
"chalk": "^2.4.2",
"coinstring": "^2.3.0",
"colorsys": "^1.0.22",
"ejs": "^2.6.1",
"inquirer": "^6.3.1",
"lunicode": "^2.0.1",
"parse5": "^5.1.0",
"tmp": "^0.1.0",
"validator": "^10.11.0",
"wrap-ansi": "^5.1.0"
},
"devDependencies": {
"eslint": "^5.16.0",
"jest": "^24.8.0"
},
"repository": "git@github.com:schulterklopfer/cyphernodeconf.git",
"license": "MIT"
}

View File

@@ -0,0 +1,88 @@
const chalk = require('chalk');
const name = 'cyphernode';
const capitalise = function( txt ) {
return txt.charAt(0).toUpperCase() + txt.substr(1);
};
const prefix = function() {
return chalk.green(capitalise(name)+': ');
};
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
return [{
// https://github.com/SBoudrias/Inquirer.js#question
// input, confirm, list, rawlist, expand, checkbox, password, editor
type: 'checkbox',
name: 'features',
message: prefix()+'What features do you want to add to your cyphernode?'+utils.getHelp('features'),
choices: utils.featureChoices()
},
{
type: 'list',
name: 'net',
default: utils.getDefault( 'net' ),
message: prefix()+'What net do you want to run on?'+utils.getHelp('net'),
choices: [{
name: "Testnet",
value: "testnet"
},{
name: "Mainnet",
value: "mainnet"
}]
},
{
type: 'confirm',
name: 'run_as_different_user',
default: utils.getDefault( 'run_as_different_user' ),
message: prefix()+'Run as different user?'+utils.getHelp('run_as_different_user')
},
{
when: function( props ) {
return props.run_as_different_user;
},
type: 'input',
name: 'username',
default: utils.getDefault( 'username' ),
message: prefix()+'What username will cyphernode run under?'+utils.getHelp('username'),
filter: utils.trimFilter,
validate: utils.usernameValidator
},
{
type: 'confirm',
name: 'use_xpub',
default: utils.getDefault( 'use_xpub' )||false,
message: prefix()+'Use a default xpub key to watch or generate adresses?'+utils.getHelp('use_xpub'),
},
{
when: function( props ) {
return props.use_xpub;
},
type: 'input',
name: 'xpub',
default: utils.getDefault( 'xpub' ),
message: prefix()+'What is your default xpub key?'+utils.getHelp('xpub'),
filter: utils.trimFilter,
validate: utils.xkeyValidator
},
{
when: function( props ) {
return props.use_xpub;
},
type: 'input',
name: 'derivation_path',
default: utils.getDefault( 'derivation_path' ),
message: prefix()+'What is your default derivation path?'+utils.getHelp('derivation_path'),
filter: utils.trimFilter,
validate: utils.derivationPathValidator
}];
},
templates: function( props ) {
return [];
}
};

View File

@@ -0,0 +1,95 @@
const chalk = require('chalk');
const name = 'gatekeeper';
const capitalise = function( txt ) {
return txt.charAt(0).toUpperCase() + txt.substr(1);
};
const prefix = function() {
return chalk.green(capitalise(name)+': ');
};
const hasAuthKeys = function( props ) {
return props &&
props.gatekeeper_keys &&
props.gatekeeper_keys.configEntries &&
props.gatekeeper_keys.configEntries.length > 0;
}
const hasCert = function( props ) {
return props &&
props.gatekeeper_sslkey &&
props.gatekeeper_sslcert
}
let password = '';
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
// TODO: delete clientKeys archive when password chnages
return [{
type: 'password',
name: 'gatekeeper_clientkeyspassword',
default: utils.getDefault( 'gatekeeper_clientkeyspassword' ),
message: prefix()+'Enter a password to protect your client keys with'+utils.getHelp('gatekeeper_clientkeyspassword'),
filter: utils.trimFilter,
validate: utils.notEmptyValidator
},
{
when: function( props ) {
// hacky hack
password = props.gatekeeper_clientkeyspassword;
return true;
},
type: 'password',
name: 'gatekeeper_clientkeyspassword_c',
default: utils.getDefault( 'gatekeeper_clientkeyspassword_c' ),
message: prefix()+'Confirm your client keys password.'+utils.getHelp('gatekeeper_clientkeyspassword_c'),
filter: utils.trimFilter,
validate: function( input ) {
if(input !== password) {
throw new Error( 'Client keys passwords do not match' );
}
return true;
}
},
{
type: 'input',
name: 'gatekeeper_port',
default: utils.getDefault( 'gatekeeper_port' ),
message: prefix()+'The port gatekeeper will listen on for requests'+utils.getHelp('gatekeeper_port'),
filter: utils.trimFilter,
validate: function( port ) {
return utils.notEmptyValidator( port ) && !isNaN( parseInt(port) )
}
},
{
when: function() { return hasAuthKeys( utils.props ); },
type: 'confirm',
name: 'gatekeeper_recreatekeys',
default: false,
message: prefix()+'Recreate gatekeeper keys?'+utils.getHelp('gatekeeper_recreatekeys')
},
{
when: function() { return hasCert( utils.props ); },
type: 'confirm',
name: 'gatekeeper_recreatecert',
default: false,
message: prefix()+'Recreate gatekeeper certificate?'+utils.getHelp('gatekeeper_recreatecert')
},
{
when: function(props) { return !hasCert( utils.props ) || props.gatekeeper_recreatecert },
type: 'input',
name: 'gatekeeper_cns',
default: utils.getDefault( 'gatekeeper_cns' ),
message: prefix()+'Gatekeeper cert CNS (ips, domains, wildcard domains seperated by comma)?'+utils.getHelp('gatekeeper_cns')
}];
},
templates: function( props ) {
return [ 'keys.properties', 'api.properties', 'cert.pem', 'key.pem' ];
}
};

View File

@@ -0,0 +1,15 @@
const chalk = require('chalk');
const name = 'traefik';
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
return [];
},
templates: function( props ) {
return [ 'acme.json', 'traefik.toml', 'htpasswd' ];
}
};

View File

@@ -0,0 +1,90 @@
const path = require('path');
const chalk = require('chalk');
const name = 'lightning';
const capitalise = function( txt ) {
return txt.charAt(0).toUpperCase() + txt.substr(1);
};
const prefix = function() {
return chalk.green(capitalise(name)+': ');
};
const featureCondition = function(props) {
return props.features && props.features.indexOf( name ) != -1;
};
const templates = {
'lnd': [ path.join('lnd','lnd.conf') ],
'c-lightning': [ path.join('c-lightning','config'), path.join('c-lightning','bitcoin.conf') ]
};
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
return [
/*
{
when: featureCondition,
type: 'list',
name: 'lightning_implementation',
default: utils.getDefault( 'lightning_implementation' ),
message: prefix()+'What lightning implementation do you want to use?'+utils.getHelp('lightning_implementation'),
choices: [
{
name: 'C-lightning',
value: 'c-lightning'
},
{
name: 'LND',
value: 'lnd'
}
]
},
*/
{
when: featureCondition,
type: 'input',
name: 'lightning_external_ip',
default: utils.getDefault( 'lightning_external_ip' ),
filter: utils.trimFilter,
validate: utils.ipOrFQDNValidator,
message: prefix()+'What external ip does your lightning node have?'+utils.getHelp('lightning_external_ip'),
},
{
when: featureCondition,
type: 'input',
name: 'lightning_nodename',
default: utils.getDefault( 'lightning_nodename' ),
filter: utils.trimFilter,
validate: (input)=>{
if( !input.trim() ) {
return true;
}
return utils.lightningNodeNameValidator(input);
},
message: prefix()+'What name has your lightning node?'+utils.getHelp('lightning_nodename'),
},
{
when: featureCondition,
type: 'input',
name: 'lightning_nodecolor',
default: utils.getDefault( 'lightning_nodecolor' ),
filter: utils.trimFilter,
validate: (input)=>{
if( !input.trim() ) {
return true;
}
return utils.colorValidator(input);
},
message: prefix()+'What color has your lightning node?'+utils.getHelp('lightning_nodecolor'),
}];
},
templates: function( props ) {
return featureCondition(props)?templates[props.lightning_implementation]:[];
}
};

View File

@@ -0,0 +1,109 @@
const chalk = require('chalk');
const name = 'bitcoin';
const capitalise = function( txt ) {
return txt.charAt(0).toUpperCase() + txt.substr(1);
};
const prefix = function() {
return chalk.green(capitalise(name)+': ');
};
const bitcoinExternal = function(props) {
return props.bitcoin_mode === 'external'
};
const bitcoinInternal = function(props) {
return props.bitcoin_mode === 'internal'
};
const bitcoinInternalAndPrune = function(props) {
return bitcoinInternal(props) && props.bitcoin_prune;
};
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
return [
{
type: 'list',
name: 'bitcoin_mode',
default: utils.getDefault( 'bitcoin_mode' ),
message: prefix()+'Cyphernode will manage your bitcoin full node.'+utils.getHelp('bitcoin_mode'),
choices: [
{
name: 'Ok. That is awesome',
value: 'internal'
}
]
},
{
when: bitcoinExternal,
type: 'input',
name: 'bitcoin_node_ip',
default: utils.getDefault( 'bitcoin_node_ip' ),
filter: utils.trimFilter,
validate: utils.ipOrFQDNValidator,
message: prefix()+'What is your full node ip address?'+utils.getHelp('bitcoin_node_ip'),
},
{
type: 'input',
name: 'bitcoin_rpcuser',
default: utils.getDefault( 'bitcoin_rpcuser' ),
message: prefix()+'Name of bitcoin rpc user?'+utils.getHelp('bitcoin_rpcuser'),
filter: utils.trimFilter,
},
{
type: 'password',
name: 'bitcoin_rpcpassword',
default: utils.getDefault( 'bitcoin_rpcpassword' ),
message: prefix()+'Password of bitcoin rpc user?'+utils.getHelp('bitcoin_rpcpassword'),
filter: utils.trimFilter,
},
{
when: function(props) {
return bitcoinInternal( props ) && props.features.indexOf('lightning') === -1;
},
type: 'confirm',
name: 'bitcoin_prune',
default: utils.getDefault( 'bitcoin_prune' ),
message: prefix()+'Run bitcoin node in prune mode?'+utils.getHelp('bitcoin_prune'),
},
{
when: function(props) {
return bitcoinInternalAndPrune( props ) && props.features.indexOf('lightning') === -1;
},
type: 'input',
name: 'bitcoin_prune_size',
default: utils.getDefault( 'bitcoin_prune_size' ),
message: prefix()+'What is the maximum size of your blockchain data in megabytes?'+utils.getHelp('bitcoin_prune_size'),
validate: function( input ) {
if( ! /^\d+$/.test(input) ) {
throw new Error( "Not a number");
}
if( input < 550 ) {
throw new Error( "At least 550 is required");
}
return true;
}
}, // TODO: ask for size of prune
{
when: bitcoinInternal,
type: 'input',
name: 'bitcoin_uacomment',
default: utils.getDefault( 'bitcoin_uacomment' ),
message: prefix()+'Any UA comment?'+utils.getHelp('bitcoin_uacomment'),
filter: utils.trimFilter,
validate: (input)=> {return utils.optional(input,utils.UACommentValidator) }
}];
},
env: function( props ) {
return 'VAR0=VALUE0\nVAR1=VALUE1'
},
templates: function( props ) {
return ['bitcoin.conf']
}
};

View File

@@ -0,0 +1,303 @@
const path = require('path');
const chalk = require('chalk');
const name = 'installer';
const capitalise = function( txt ) {
return txt.charAt(0).toUpperCase() + txt.substr(1);
};
const prefix = function() {
return chalk.green(capitalise(name)+': ');
};
const installerDocker = function(props) {
return props.installer_mode === 'docker'
};
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
return [{
type: 'list',
name: 'installer_mode',
default: utils.getDefault( 'installer_mode' ),
message: prefix()+chalk.red('Where do you want to install cyphernode?')+utils.getHelp('installer_mode'),
choices: [{
name: "Docker",
value: "docker"
}]
},
{
when: installerDocker,
type: 'list',
name: 'traefik_datapath',
default: utils.getDefault( 'traefik_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/traefik",
value: utils.setupDir()+"/cyphernode/traefik"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/traefik",
value: utils.defaultDataDirBase()+"/cyphernode/traefik"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/traefik",
value: utils.defaultDataDirBase()+"/.cyphernode/traefik"
},
{
name: utils.defaultDataDirBase()+"/traefik",
value: utils.defaultDataDirBase()+"/traefik"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your traefik data?'+utils.getHelp('traefik_datapath'),
},
{
when: (props)=>{ return installerDocker(props) && (props.traefik_datapath === '_custom') },
type: 'input',
name: 'traefik_datapath_custom',
default: utils.getDefault( 'traefik_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for traefik data?'+utils.getHelp('traefik_datapath_custom'),
},
{
when: installerDocker,
type: 'list',
name: 'gatekeeper_datapath',
default: utils.getDefault( 'gatekeeper_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/gatekeeper",
value: utils.setupDir()+"/cyphernode/gatekeeper"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/gatekeeper",
value: utils.defaultDataDirBase()+"/cyphernode/gatekeeper"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/gatekeeper",
value: utils.defaultDataDirBase()+"/.cyphernode/gatekeeper"
},
{
name: utils.defaultDataDirBase()+"/gatekeeper",
value: utils.defaultDataDirBase()+"/gatekeeper"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your gatekeeper data?'+utils.getHelp('gatekeeper_datapath'),
},
{
when: (props)=>{ return installerDocker(props) && (props.gatekeeper_datapath === '_custom') },
type: 'input',
name: 'gatekeeper_datapath_custom',
default: utils.getDefault( 'gatekeeper_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for gatekeeper data?'+utils.getHelp('gatekeeper_datapath_custom'),
},
{
when: installerDocker,
type: 'list',
name: 'proxy_datapath',
default: utils.getDefault( 'proxy_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/proxy",
value: utils.setupDir()+"/cyphernode/proxy"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/proxy",
value: utils.defaultDataDirBase()+"/cyphernode/proxy"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/proxy",
value: utils.defaultDataDirBase()+"/.cyphernode/proxy"
},
{
name: utils.defaultDataDirBase()+"/proxy",
value: utils.defaultDataDirBase()+"/proxy"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your proxy data?'+utils.getHelp('proxy_datapath'),
},
{
when: (props)=>{ return installerDocker(props) && (props.proxy_datapath === '_custom') },
type: 'input',
name: 'proxy_datapath_custom',
default: utils.getDefault( 'proxy_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for your proxy data?'+utils.getHelp('proxy_datapath_custom'),
},
{
when: function(props) { return installerDocker(props) && props.bitcoin_mode === 'internal' },
type: 'list',
name: 'bitcoin_datapath',
default: utils.getDefault( 'bitcoin_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/bitcoin",
value: utils.setupDir()+"/cyphernode/bitcoin"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/bitcoin",
value: utils.defaultDataDirBase()+"/cyphernode/bitcoin"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/bitcoin",
value: utils.defaultDataDirBase()+"/.cyphernode/bitcoin"
},
{
name: utils.defaultDataDirBase()+"/bitcoin",
value: utils.defaultDataDirBase()+"/bitcoin"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your bitcoin full node data?'+utils.getHelp('bitcoin_datapath'),
},
{
when: function(props) { return installerDocker(props) && props.bitcoin_mode === 'internal' && props.bitcoin_datapath === '_custom' },
type: 'input',
name: 'bitcoin_datapath_custom',
default: utils.getDefault( 'bitcoin_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for your bitcoin full node data?'+utils.getHelp('bitcoin_datapath_custom'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('lightning') !== -1 },
type: 'list',
name: 'lightning_datapath',
default: utils.getDefault( 'lightning_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/lightning",
value: utils.setupDir()+"/cyphernode/lightning"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/lightning",
value: utils.defaultDataDirBase()+"/cyphernode/lightning"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/lightning",
value: utils.defaultDataDirBase()+"/.cyphernode/lightning"
},
{
name: utils.defaultDataDirBase()+"/lightning",
value: utils.defaultDataDirBase()+"/lightning"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your lightning node data?'+utils.getHelp('lightning_datapath'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('lightning') !== -1 && props.lightning_datapath === '_custom'},
type: 'input',
name: 'lightning_datapath_custom',
default: utils.getDefault( 'lightning_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for your lightning node data?'+utils.getHelp('lightning_datapath_custom'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('otsclient') !== -1 },
type: 'list',
name: 'otsclient_datapath',
default: utils.getDefault( 'otsclient_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/otsclient",
value: utils.setupDir()+"/cyphernode/otsclient"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/otsclient",
value: utils.defaultDataDirBase()+"/cyphernode/otsclient"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/otsclient",
value: utils.defaultDataDirBase()+"/.cyphernode/otsclient"
},
{
name: utils.defaultDataDirBase()+"/otsclient",
value: utils.defaultDataDirBase()+"/otsclient"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your OTS data?'+utils.getHelp('otsclient_datapath'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('otsclient') !== -1 && props.otsclient_datapath === '_custom' },
type: 'input',
name: 'otsclient_datapath_custom',
default: utils.getDefault( 'otsclient_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Where is your otsclient data?'+utils.getHelp('otsclient_datapath_custom'),
},
{
when: function(props) { return installerDocker(props) && props.bitcoin_mode === 'internal' },
type: 'confirm',
name: 'bitcoin_expose',
default: utils.getDefault( 'bitcoin_expose' ),
message: prefix()+'Expose bitcoin full node outside of the docker network?'+utils.getHelp('bitcoin_expose'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('lightning') !== -1 },
type: 'confirm',
name: 'lightning_expose',
default: utils.getDefault( 'lightning_expose' ),
message: prefix()+'Expose lightning node outside of the docker network?'+utils.getHelp('lightning_expose'),
},
{
when: installerDocker,
type: 'list',
name: 'docker_mode',
default: utils.getDefault( 'docker_mode' ),
message: prefix()+'What docker mode: docker swarm or docker-compose?'+utils.getHelp('docker_mode'),
choices: [{
name: "docker swarm",
value: "swarm"
},
{
name: "docker-compose",
value: "compose"
}]
},
{
type: 'confirm',
name: 'installer_cleanup',
default: utils.getDefault( 'installer_cleanup' ),
message: prefix()+'Cleanup installer after installation?'+utils.getHelp('installer_cleanup'),
}];
},
templates: function( props ) {
if( props.installer_mode === 'docker' ) {
return ['config.sh','start.sh', 'stop.sh', 'testfeatures.sh', path.join('docker', 'docker-compose.yaml')];
}
return ['config.sh','start.sh', 'stop.sh', 'testfeatures.sh'];
}
};

3
cyphernodeconf_docker/run.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
SETUP_DIR=/Users/jash/src/cyphernode_satoshiportal/dist DEFAULT_CERT_HOSTNAME=disk0book.local PROXYCRON_VERSION=v0.2.0-rc.5 PYCOIN_VERSION=v0.2.0-rc.5 SETUP_VERSION=v0.2.0-rc.5 BITCOIN_VERSION=v0.17.1 LIGHTNING_VERSION=v0.7.0 DEFAULT_DATADIR_BASE=/Users/jash GATEKEEPER_VERSION=v0.2.0-rc.5 PROXY_VERSION=v0.2.0-rc.5 OTSCLIENT_VERSION=v0.2.0-rc.5 DEFAULT_USER=jash EDITOR=/usr/bin/nano node index.js $@

View File

@@ -0,0 +1,456 @@
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://cyphernode.io/config-v0.1.0.json",
"type": "object",
"title": "Cyphernode config file structure v0.1.0",
"additionalProperties": false,
"required": [
"__version",
"features",
"net",
"xpub",
"derivation_path",
"installer_mode",
"run_as_different_user",
"username",
"docker_mode",
"bitcoin_rpcuser",
"bitcoin_rpcpassword",
"bitcoin_prune",
"bitcoin_datapath",
"bitcoin_node_ip",
"bitcoin_mode",
"bitcoin_expose",
"gatekeeper_apiproperties",
"gatekeeper_keys",
"gatekeeper_sslcert",
"gatekeeper_sslkey",
"gatekeeper_cns",
"gatekeeper_clientkeyspassword",
"gatekeeper_clientkeyspassword_c",
"gatekeeper_statuspw",
"gatekeeper_datapath",
"lightning_expose",
"lightning_implementation",
"lightning_datapath",
"lightning_nodename",
"lightning_nodecolor",
"otsclient_datapath",
"proxy_datapath"
],
"allOf": [
{
"if": {
"properties": {
"run_as_different_user": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"username"
]
}
},
{
"if": {
"properties": {
"bitcoin_prune": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"bitcoin_prune_size"
]
}
}
],
"properties": {
"__version": {
"type": "string",
"enum": ["0.1.0"],
"default": "0.1.0",
"examples": ["0.1.0"]
},
"features": {
"$id": "#/properties/features",
"type": "array",
"title": "The optional features of this cyphernode",
"default": [],
"items": {
"$id": "#/properties/features/items",
"type": "string",
"enum": [
"lightning",
"otsclient"
],
"title": "The feature",
"examples": [
"lightning",
"otsclient"
]
}
},
"net": {
"$id": "#/properties/net",
"type": "string",
"enum": [
"testnet",
"mainnet"
],
"title": "The net cyphernode is running on",
"default": "testnet",
"examples": [
"testnet"
]
},
"xpub": {
"$id": "#/properties/xpub",
"type": "string",
"title": "Default xpub to derive addresses from",
"pattern": "^(\\w+)$"
},
"derivation_path": {
"$id": "#/properties/derivation_path",
"type": "string",
"title": "Default derivation path",
"default": "0/n",
"examples": [
"0/n"
]
},
"installer_mode": {
"$id": "#/properties/installer_mode",
"type": "string",
"enum": [
"docker"
],
"title": "Install mode",
"default": "docker",
"examples": [
"docker"
]
},
"run_as_different_user": {
"$id": "#/properties/run_as_different_user",
"type": "boolean",
"title": "Run as different user",
"default": true,
"examples": [
true
]
},
"username": {
"$id": "#/properties/username",
"type": "string",
"title": "Username to run under",
"default": "cyphernode",
"examples": [
"cyphernode"
]
},
"docker_mode": {
"$id": "#/properties/docker_mode",
"type": "string",
"enum": [
"swarm",
"compose"
],
"title": "How to run the containers",
"default": "compose",
"examples": [
"compose"
]
},
"bitcoin_rpcuser": {
"$id": "#/properties/bitcoin_rpcuser",
"type": "string",
"title": "Bitcoin rpc user",
"default": "bitcoin",
"examples": [
"bitcoin"
]
},
"bitcoin_rpcpassword": {
"$id": "#/properties/bitcoin_rpcpassword",
"type": "string",
"title": "Bitcoin rpc password",
"default": "CHANGEME",
"examples": [
"CHANGEME"
]
},
"bitcoin_uacomment": {
"$id": "#/properties/bitcoin_uacomment",
"type": "string",
"title": "Bitcoin user agent comment",
"examples": [
"cyphernode"
]
},
"bitcoin_prune": {
"$id": "#/properties/bitcoin_prune",
"type": "boolean",
"title": "Bitcoin prune",
"default": false,
"examples": [
"false"
]
},
"bitcoin_prune_size": {
"$id": "#/properties/bitcoin_prune_size",
"type": "integer",
"title": "Bitcoin prune size",
"default": 550,
"examples": [
550
]
},
"bitcoin_datapath": {
"$id": "#/properties/bitcoin_datapath",
"type": "string",
"title": "Bitcoin datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"bitcoin_datapath_custom": {
"$id": "#/properties/bitcoin_datapath_custom",
"type": "string",
"title": "Bitcoin custom datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"lightning_datapath": {
"$id": "#/properties/lightning_datapath",
"type": "string",
"title": "Lightning datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"lightning_datapath_custom": {
"$id": "#/properties/lightning_datapath_custom",
"type": "string",
"title": "Lightning custom datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"proxy_datapath": {
"$id": "#/properties/proxy_datapath",
"type": "string",
"title": "Proxy datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"proxy_datapath_custom": {
"$id": "#/properties/proxy_datapath_custom",
"type": "string",
"title": "Proxy custom datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"otsclient_datapath": {
"$id": "#/properties/otsclient_datapath",
"type": "string",
"title": "OTS Client datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"otsclient_datapath_custom": {
"$id": "#/properties/otsclient_datapath_custom",
"type": "string",
"title": "OTS Client custom datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"bitcoin_node_ip": {
"$id": "#/properties/bitcoin_node_ip",
"type": "string",
"format": "ipv4",
"title": "Bitcoin node ip",
"examples": [
"123.123.123.123"
]
},
"bitcoin_mode": {
"$id": "#/properties/bitcoin_mode",
"type": "string",
"enum": [
"internal"
],
"title": "Bitcoin mode",
"examples": [
"internal"
]
},
"bitcoin_expose": {
"$id": "#/properties/bitcoin_expose",
"type": "boolean",
"title": "Expose bitcoin node",
"default": false,
"examples": [
false
]
},
"lightning_expose": {
"$id": "#/properties/lightning_expose",
"type": "boolean",
"title": "Expose lightning node",
"default": false,
"examples": [
false
]
},
"gatekeeper_datapath": {
"$id": "#/properties/gatekeeper_datapath",
"type": "string",
"title": "Gatekeeper datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_datapath_custom": {
"$id": "#/properties/gatekeeper_datapath_custom",
"type": "string",
"title": "Gatekeeper custom datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_apiproperties": {
"$id": "#/properties/gatekeeper_apiproperties",
"type": "string",
"title": "API properties",
"examples": [
"# Stats can:\naction_getblockchaininfo=stats\n\n# Watcher can:\naction_watch=watcher\naction_unwatch=watcher\naction_watchxpub=watcher\naction_unwatchxpubbyxpub=watcher\naction_unwatchxpubbylabel=watcher\naction_getactivewatchesbyxpub=watcher\naction_getactivewatchesbylabel=watcher\naction_getactivexpubwatches=watcher\naction_watchtxid=watcher\naction_getactivewatches=watcher\naction_getbestblockhash=watcher\naction_getbestblockinfo=watcher\naction_getblockinfo=watcher\naction_gettransaction=watcher\naction_ln_getinfo=watcher\naction_ln_create_invoice=watcher\naction_ln_getconnectionstring=watcher\naction_ln_decodebolt11=watcher\n\n# Spender can do what the watcher can do, plus:\naction_getbalance=spender\naction_getbalancebyxpub=spender\naction_getbalancebyxpublabel=spender\naction_getnewaddress=spender\naction_spend=spender\naction_addtobatch=spender\naction_batchspend=spender\naction_deriveindex=spender\naction_derivepubpath=spender\naction_ln_pay=spender\naction_ln_newaddr=spender\naction_ots_stamp=spender\naction_ots_getfile=spender\naction_ln_getinvoice=spender\naction_ln_decodebolt11=spender\naction_ln_connectfund=spender\n\n# Admin can do what the spender can do, plus:\n\n\n# Should be called from inside the Docker network only:\naction_conf=internal\naction_newblock=internal\naction_executecallbacks=internal\naction_ots_backoffice=internal"
]
},
"gatekeeper_keys": {
"$id": "#/properties/gatekeeper_keys",
"type": "object",
"title": "Gatekeeper keys",
"default": {
"configEntries": [],
"clientInformation": []
},
"required": [
"configEntries",
"clientInformation"
],
"properties": {
"configEntries": {
"$id": "#/properties/gatekeeper_keys/configEntries",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/configEntries/entry",
"type": "string",
"pattern": "^kapi_id=\".+\";kapi_key=\".+\";kapi_groups=\".+\";.+$"
},
"examples": [
[
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
]
]
},
"clientInformation": {
"$id": "#/properties/gatekeeper_keys/clientInformation",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/clientInformation/entry",
"type": "string",
"pattern": "^.+=.+$"
},
"examples": [
[
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
]
}
}
},
"gatekeeper_clientkeyspassword": {
"$id": "#/properties/gatekeeper_clientkeyspassword",
"type": "string",
"title": "Password for the encrypted client keys archive"
},
"gatekeeper_clientkeyspassword_c": {
"$id": "#/properties/gatekeeper_clientkeyspassword",
"type": "string",
"title": "Password for the encrypted client keys archive, verified"
},
"gatekeeper_statuspw": {
"$id": "#/properties/adminhash",
"type": "string",
"title": "MD5 hash of admin password"
},
"gatekeeper_sslcert": {
"$id": "#/properties/gatekeeper_sslcert",
"type": "string",
"title": "Gatekeeper SSL Cert"
},
"gatekeeper_sslkey": {
"$id": "#/properties/gatekeeper_sslkey",
"type": "string",
"title": "Gatekeeper SSL Key"
},
"gatekeeper_cns": {
"$id": "#/properties/gatekeeper_cns",
"type": "string",
"title": "Gatekeeper cns",
"examples": [
"myhost.mydomain.com,*.myotherdomain.com,123.123.123.123"
]
},
"lightning_implementation": {
"$id": "#/properties/lightning_implementation",
"type": "string",
"enum": [
"c-lightning"
],
"title": "The lightning implementation",
"default": "c-lightning",
"examples": [
"c-lightning"
]
},
"lightning_nodename": {
"$id": "#/properties/lightning_nodename",
"type": "string",
"title": "The lightning node name",
"examples": [
"🚀 Mighty Moose 🚀"
]
},
"lightning_nodecolor": {
"$id": "#/properties/lightning_nodecolor",
"type": "string",
"pattern": "^[0-9A-Fa-f]{6}$",
"title": "The lightning node color",
"default": "ff0000",
"examples": [
"ff0000",
"00ff00",
"00ffff"
]
}
}
}

View File

@@ -0,0 +1,515 @@
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://cyphernode.io/config-v0.2.0.json",
"type": "object",
"title": "Cyphernode config file structure v0.2.0",
"additionalProperties": false,
"required": [
"schema_version",
"setup_version",
"features",
"net",
"use_xpub",
"installer_mode",
"run_as_different_user",
"docker_mode",
"docker_versions",
"adminhash",
"bitcoin_rpcuser",
"bitcoin_rpcpassword",
"bitcoin_prune",
"bitcoin_datapath",
"bitcoin_mode",
"bitcoin_expose",
"gatekeeper_keys",
"gatekeeper_sslcert",
"gatekeeper_sslkey",
"gatekeeper_cns",
"gatekeeper_clientkeyspassword",
"gatekeeper_datapath",
"gatekeeper_port",
"lightning_expose",
"lightning_implementation",
"lightning_datapath",
"lightning_nodename",
"lightning_nodecolor",
"proxy_datapath",
"otsclient_datapath",
"traefik_datapath"
],
"allOf": [
{
"if": {
"properties": {
"run_as_different_user": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"username"
]
}
},
{
"if": {
"properties": {
"use_xpub": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"xpub",
"derivation_path"
]
}
},
{
"if": {
"properties": {
"bitcoin_prune": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"bitcoin_prune_size"
]
}
}
],
"properties": {
"schema_version": {
"type": "string",
"enum": [
"0.2.0"
],
"default": "0.2.0",
"examples": [
"0.2.0"
]
},
"setup_version": {
"type": "string",
"examples": [
"v0.2.0"
]
},
"docker_versions": {
"$id": "#/properties/dockerVersions",
"type": "object",
"title": "All versions of the docker containers",
"default": {},
"additionalProperties": {
"type": "string"
}
},
"features": {
"$id": "#/properties/features",
"type": "array",
"title": "The optional features of this cyphernode",
"default": [],
"items": {
"$id": "#/properties/features/items",
"type": "string",
"enum": [
"lightning",
"otsclient"
],
"title": "The feature",
"default": "",
"examples": [
"lightning",
"otsclient"
]
}
},
"net": {
"$id": "#/properties/net",
"type": "string",
"enum": [
"testnet",
"mainnet"
],
"title": "The net cyphernode is running on",
"default": "testnet",
"examples": [
"testnet"
]
},
"use_xpub": {
"$id": "#/properties/use_xpub",
"type": "boolean",
"title": "Use xpub key?",
"default": false,
"examples": [
false
]
},
"xpub": {
"$id": "#/properties/xpub",
"type": "string",
"title": "Default xpub to derive addresses from",
"pattern": "^(\\w+)$"
},
"derivation_path": {
"$id": "#/properties/derivation_path",
"type": "string",
"title": "Default derivation path",
"default": "0/n",
"examples": [
"0/n"
]
},
"installer_mode": {
"$id": "#/properties/installer_mode",
"type": "string",
"enum": [
"docker"
],
"title": "Install mode",
"default": "docker",
"examples": [
"docker"
]
},
"run_as_different_user": {
"$id": "#/properties/run_as_different_user",
"type": "boolean",
"title": "Run as different user",
"default": true,
"examples": [
true
]
},
"username": {
"$id": "#/properties/username",
"type": "string",
"title": "Username to run under",
"default": "cyphernode",
"examples": [
"cyphernode"
]
},
"docker_mode": {
"$id": "#/properties/docker_mode",
"type": "string",
"enum": [
"swarm",
"compose"
],
"title": "How to run the containers",
"default": "compose",
"examples": [
"compose"
]
},
"bitcoin_rpcuser": {
"$id": "#/properties/bitcoin_rpcuser",
"type": "string",
"title": "Bitcoin rpc user",
"default": "bitcoin",
"examples": [
"bitcoin"
]
},
"bitcoin_rpcpassword": {
"$id": "#/properties/bitcoin_rpcpassword",
"type": "string",
"title": "Bitcoin rpc password",
"default": "CHANGEME",
"examples": [
"CHANGEME"
]
},
"bitcoin_uacomment": {
"$id": "#/properties/bitcoin_uacomment",
"type": "string",
"title": "Bitcoin user agent comment",
"examples": [
"cyphernode"
]
},
"bitcoin_prune": {
"$id": "#/properties/bitcoin_prune",
"type": "boolean",
"title": "Bitcoin prune",
"default": false,
"examples": [
"false"
]
},
"bitcoin_prune_size": {
"$id": "#/properties/bitcoin_prune_size",
"type": "integer",
"title": "Bitcoin prune size",
"default": 550,
"examples": [
550
]
},
"bitcoin_datapath": {
"$id": "#/properties/bitcoin_datapath",
"type": "string",
"title": "Bitcoin datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"bitcoin_datapath_custom": {
"$id": "#/properties/bitcoin_datapath_custom",
"type": "string",
"title": "Bitcoin custom datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"lightning_datapath": {
"$id": "#/properties/lightning_datapath",
"type": "string",
"title": "Lightning datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"lightning_datapath_custom": {
"$id": "#/properties/lightning_datapath_custom",
"type": "string",
"title": "Lightning custom datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"proxy_datapath": {
"$id": "#/properties/proxy_datapath",
"type": "string",
"title": "Proxy datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"proxy_datapath_custom": {
"$id": "#/properties/proxy_datapath_custom",
"type": "string",
"title": "Proxy custom datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"otsclient_datapath": {
"$id": "#/properties/otsclient_datapath",
"type": "string",
"title": "OTS Client datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"otsclient_datapath_custom": {
"$id": "#/properties/otsclient_datapath_custom",
"type": "string",
"title": "OTS Client custom datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"traefik_datapath": {
"$id": "#/properties/traefik_datapath",
"type": "string",
"title": "Traefik datapath",
"examples": [
"/tmp/cyphernode/traefik"
]
},
"traefik_datapath_custom": {
"$id": "#/properties/traefik_datapath_custom",
"type": "string",
"title": "Traefik custom datapath",
"examples": [
"/tmp/cyphernode/traefik"
]
},
"lightning_external_ip": {
"$id": "#/properties/lightning_external_ip",
"type": "string",
"format": "ipv4",
"title": "External lightning node ip",
"examples": [
"123.123.123.123"
]
},
"bitcoin_mode": {
"$id": "#/properties/bitcoin_mode",
"type": "string",
"enum": [
"internal"
],
"title": "Bitcoin mode",
"default": "internal",
"examples": [
"internal"
]
},
"bitcoin_expose": {
"$id": "#/properties/bitcoin_expose",
"type": "boolean",
"title": "Expose bitcoin node",
"default": false,
"examples": [
false
]
},
"lightning_expose": {
"$id": "#/properties/lightning_expose",
"type": "boolean",
"title": "Expose lightning node",
"default": false,
"examples": [
false
]
},
"gatekeeper_datapath": {
"$id": "#/properties/gatekeeper_datapath",
"type": "string",
"title": "Gatekeeper datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_datapath_custom": {
"$id": "#/properties/gatekeeper_datapath_custom",
"type": "string",
"title": "Gatekeeper custom datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_port": {
"$id": "#/properties/gatekeeper_port",
"type": "integer",
"title": "Gatekeeper port",
"default": 2009,
"examples": [
2009
]
},
"gatekeeper_keys": {
"$id": "#/properties/gatekeeper_keys",
"type": "object",
"title": "Gatekeeper keys",
"default": {
"configEntries": [],
"clientInformation": []
},
"required": [
"configEntries",
"clientInformation"
],
"properties": {
"configEntries": {
"$id": "#/properties/gatekeeper_keys/configEntries",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/configEntries/entry",
"type": "string",
"pattern": "^kapi_id=\".+\";kapi_key=\".+\";kapi_groups=\".+\";.+$"
},
"examples": [
[
"kapi_id=\"000\";kapi_key=\"a27f9e73fdde6a5005879c259c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"stats\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"stats,watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"stats,watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"stats,watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
]
]
},
"clientInformation": {
"$id": "#/properties/gatekeeper_keys/clientInformation",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/clientInformation/entry",
"type": "string",
"pattern": "^.+=.+$"
},
"examples": [
[
"000=a27f9e73fdde6a5005879c259c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
]
}
}
},
"gatekeeper_sslcert": {
"$id": "#/properties/gatekeeper_sslcert",
"type": "string",
"title": "Gatekeeper SSL Cert"
},
"gatekeeper_sslkey": {
"$id": "#/properties/gatekeeper_sslkey",
"type": "string",
"title": "Gatekeeper SSL Key"
},
"gatekeeper_cns": {
"$id": "#/properties/gatekeeper_cns",
"type": "string",
"title": "Gatekeeper cns",
"examples": [
"myhost.mydomain.com,*.myotherdomain.com,123.123.123.123"
]
},
"gatekeeper_clientkeyspassword": {
"$id": "#/properties/gatekeeper_clientkeyspassword",
"type": "string",
"title": "Password for the encrypted client keys archive"
},
"adminhash": {
"$id": "#/properties/adminhash",
"type": "string",
"title": "Bcrypted hash of admin password"
},
"lightning_implementation": {
"$id": "#/properties/lightning_implementation",
"type": "string",
"enum": [
"c-lightning"
],
"title": "The lightning implementation",
"default": "c-lightning",
"examples": [
"c-lightning"
]
},
"lightning_nodename": {
"$id": "#/properties/lightning_nodename",
"type": "string",
"title": "The lightning node name",
"examples": [
"🚀 Mighty Moose 🚀"
]
},
"lightning_nodecolor": {
"$id": "#/properties/lightning_nodecolor",
"type": "string",
"pattern": "^[0-9A-Fa-f]{6}$",
"title": "The lightning node color",
"examples": [
"ff0000",
"00ff00",
"00ffff"
]
}
}
}

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,25 @@
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         

View File

@@ -0,0 +1,46 @@
<% if (net === 'testnet') { %>
# testnet
testnet=1
<% } %>
<% if (bitcoin_prune && bitcoin_mode === 'internal') { %>
prune=<%= bitcoin_prune_size || 550 %>
<% } else { %>
txindex=1
<% } %>
zmqpubrawblock=tcp://0.0.0.0:18501
zmqpubrawtx=tcp://0.0.0.0:18502
#tor
#proxy=127.0.0.1:9050
#listen=1
maxmempool=64
dbcache=64
rpcuser=<%= bitcoin_rpcuser %>
rpcpassword=<%= bitcoin_rpcpassword %>
# ATTENTION: VERY DANGEROUS OUTSIDE THE DOCKER NETWORK
rpcallowip=0.0.0.0/0
server=1
<% if (net === 'testnet') { %>
test.wallet=watching01.dat
test.wallet=xpubwatching01.dat
test.wallet=spending01.dat
test.wallet=ln01.dat
<% } else { %>
main.wallet=watching01.dat
main.wallet=xpubwatching01.dat
main.wallet=spending01.dat
main.wallet=ln01.dat
<% } %>
walletnotify=/usr/bin/curl proxy:8888/conf/%s
blocknotify=/usr/bin/curl proxy:8888/newblock/%s
<% if ( bitcoin_uacomment != null && bitcoin_uacomment != '' ) { %>
uacomment=<%= bitcoin_uacomment %>
<% } %>

View File

@@ -0,0 +1,54 @@
# Watcher can do stuff
# Spender can do what the watcher can do plus more stuff
# Admin can do what the spender can do plus even more stuff
# Stats can:
action_getblockchaininfo=stats
# Watcher can:
action_watch=watcher
action_unwatch=watcher
action_watchxpub=watcher
action_unwatchxpubbyxpub=watcher
action_unwatchxpubbylabel=watcher
action_getactivewatchesbyxpub=watcher
action_getactivewatchesbylabel=watcher
action_getactivexpubwatches=watcher
action_watchtxid=watcher
action_getactivewatches=watcher
action_getbestblockhash=watcher
action_getbestblockinfo=watcher
action_getblockinfo=watcher
action_gettransaction=watcher
action_ln_getinfo=watcher
action_ln_create_invoice=watcher
action_ln_getconnectionstring=watcher
action_ln_decodebolt11=watcher
# Spender can do what the watcher can do, plus:
action_getbalance=spender
action_getbalancebyxpub=spender
action_getbalancebyxpublabel=spender
action_getnewaddress=spender
action_spend=spender
action_addtobatch=spender
action_batchspend=spender
action_deriveindex=spender
action_derivepubpath=spender
action_ln_pay=spender
action_ln_newaddr=spender
action_ots_stamp=spender
action_ots_getfile=spender
action_ln_getinvoice=spender
action_ln_decodebolt11=spender
action_ln_connectfund=spender
# Admin can do what the spender can do, plus:
# Should be called from inside the Docker network only:
action_conf=internal
action_newblock=internal
action_executecallbacks=internal
action_ots_backoffice=internal

View File

@@ -0,0 +1 @@
<%- gatekeeper_sslcert %>

View File

@@ -0,0 +1 @@
<%- gatekeeper_sslkey %>

View File

@@ -0,0 +1 @@
<%- gatekeeper_keys.configEntries.join('\n') %>

View File

@@ -0,0 +1,21 @@
INSTALLER_MODE=<%= installer_mode %>
BITCOIN_INTERNAL=<%= (bitcoin_mode==="internal"?'true':'false') %>
FEATURE_LIGHTNING=<%= (features.indexOf('lightning') != -1)?'true':'false' %>
FEATURE_OTSCLIENT=<%= (features.indexOf('otsclient') != -1)?'true':'false' %>
LIGHTNING_IMPLEMENTATION=<%= lightning_implementation %>
PROXY_DATAPATH=<%= proxy_datapath %>
GATEKEEPER_DATAPATH=<%= gatekeeper_datapath %>
TRAEFIK_DATAPATH=<%= traefik_datapath %>
DOCKER_MODE=<%= docker_mode %>
RUN_AS_USER=<%= run_as_different_user?username:'' %>
CLEANUP=<%= installer_cleanup?'true':'false' %>
SHARED_HTPASSWD_PATH=<%= traefik_datapath %>/htpasswd
<% if ( features.indexOf('lightning') !== -1 && lightning_implementation === 'c-lightning' ) { %>
LIGHTNING_DATAPATH=<%= lightning_datapath %>
<% } %>
<% if ( features.indexOf('otsclient') !== -1 ) { %>
OTSCLIENT_DATAPATH=<%= otsclient_datapath %>
<% } %>
<% if ( bitcoin_mode==="internal" ) { %>
BITCOIN_DATAPATH=<%= bitcoin_datapath %>
<% } %>

View File

@@ -0,0 +1,200 @@
version: "3"
services:
gatekeeper:
# HTTP authentication API gate
environment:
- "TRACING=1"
image: cyphernode/gatekeeper:<%= gatekeeper_version %>
ports:
- "<%= gatekeeper_port %>:443"
volumes:
- "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs"
- "<%= gatekeeper_datapath %>/private:/etc/ssl/private"
- "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties"
- "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties"
- "<%= gatekeeper_datapath %>/htpasswd:/etc/nginx/conf.d/status/htpasswd"
- "<%= gatekeeper_datapath %>/installation.json:/etc/nginx/conf.d/s/stats/installation.json"
- "<%= gatekeeper_datapath %>/client.7z:/etc/nginx/conf.d/s/stats/client.7z"
- "<%= gatekeeper_datapath %>/config.7z:/etc/nginx/conf.d/s/stats/config.7z"
command: $USER
# deploy:
# placement:
# constraints: [node.hostname==dev]
networks:
- cyphernodenet
- cyphernodeappsnet
restart: always
traefik:
image: traefik:v1.7.9-alpine
restart: always
ports:
- 80:80
- 443:443
# deploy:
# placement:
# constraints: [node.hostname==dev]
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "<%= traefik_datapath%>/traefik.toml:/traefik.toml"
- "<%= traefik_datapath%>/acme.json:/acme.json"
- "<%= traefik_datapath%>/htpasswd:/htpasswd/htpasswd"
networks:
- cyphernodeappsnet
proxy:
command: $USER ./startproxy.sh
# Bitcoin Mini Proxy
environment:
- "TRACING=1"
- "WATCHER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet"
- "WATCHER_BTC_NODE_DEFAULT_WALLET=watching01.dat"
- "WATCHER_BTC_NODE_XPUB_WALLET=xpubwatching01.dat"
- "WATCHER_BTC_NODE_RPC_USER=<%= bitcoin_rpcuser %>:<%= bitcoin_rpcpassword %>"
- "WATCHER_BTC_NODE_RPC_CFG=/tmp/watcher_btcnode_curlcfg.properties"
- "SPENDER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>:<%= (net === 'mainnet')?'8332':'18332' %>/wallet"
- "SPENDER_BTC_NODE_DEFAULT_WALLET=spending01.dat"
- "SPENDER_BTC_NODE_RPC_USER=<%= bitcoin_rpcuser %>:<%= bitcoin_rpcpassword %>"
- "SPENDER_BTC_NODE_RPC_CFG=/tmp/spender_btcnode_curlcfg.properties"
- "PROXY_LISTENING_PORT=8888"
- "DB_PATH=/proxy/db"
- "DB_FILE=/proxy/db/proxydb"
- "PYCOIN_CONTAINER=pycoin:7777"
<% if ( use_xpub && xpub ) { %>
- "DERIVATION_PUB32=<%= xpub %>"
- "DERIVATION_PATH=<%= derivation_path %>"
<% } %>
- "WATCHER_BTC_NODE_PRUNED=<%= bitcoin_prune?'true':'false' %>"
- "OTSCLIENT_CONTAINER=otsclient:6666"
- "OTS_FILES=/proxy/otsfiles"
- "XPUB_DERIVATION_GAP=100"
image: cyphernode/proxy:<%= proxy_version %>
<% if ( devmode ) { %>
ports:
- "8888:8888"
<% } %>
volumes:
- "<%= proxy_datapath %>:/proxy/db"
<% if ( features.indexOf('lightning') !== -1 && lightning_implementation === 'c-lightning' ) { %>
- "<%= lightning_datapath %>:/.lightning"
<% } %>
<% if ( features.indexOf('otsclient') !== -1 ) { %>
- "<%= otsclient_datapath %>:/proxy/otsfiles"
<% } %>
# deploy:
# placement:
# constraints: [node.hostname==dev]
networks:
- cyphernodenet
restart: always
proxycron:
environment:
- "TX_CONF_URL=proxy:8888/executecallbacks"
- "OTS_URL=proxy:8888/ots_backoffice"
image: cyphernode/proxycron:<%= proxycron_version %>
# deploy:
# placement:
# constraints: [node.hostname==dev]
networks:
- cyphernodenet
restart: always
pycoin:
# Pycoin
command: $USER ./startpycoin.sh
image: cyphernode/pycoin:<%= pycoin_version %>
environment:
- "TRACING=1"
- "PYCOIN_LISTENING_PORT=7777"
<% if ( devmode ) { %>
ports:
- "7777:7777"
<% } %>
# deploy:
# placement:
# constraints: [node.hostname==dev]
networks:
- cyphernodenet
restart: always
<% if ( features.indexOf('lightning') !== -1 && lightning_implementation === 'c-lightning' ) { %>
lightning:
command: $USER lightningd
image: cyphernode/clightning:<%= lightning_version %>
<% if( lightning_expose ) { %>
ports:
- "9735:9735"
<% } %>
volumes:
- "<%= lightning_datapath %>:/.lightning"
- "<%= lightning_datapath %>/bitcoin.conf:/.bitcoin/bitcoin.conf"
# deploy:
# placement:
# constraints: [node.hostname==dev]
networks:
- cyphernodenet
restart: always
<% } %>
<% if ( features.indexOf('otsclient') !== -1 ) { %>
otsclient:
environment:
- "TRACING=1"
- "OTSCLIENT_LISTENING_PORT=6666"
image: cyphernode/otsclient:<%= otsclient_version %>
# deploy:
# placement:
# constraints: [node.hostname==dev]
volumes:
- "<%= otsclient_datapath %>:/otsfiles"
command: $USER /script/startotsclient.sh
networks:
- cyphernodenet
restart: always
<% } %>
<% if( bitcoin_mode === 'internal' ) { %>
bitcoin:
command: $USER bitcoind
image: cyphernode/bitcoin:<%= bitcoin_version %>
<% if( bitcoin_expose ) { %>
ports:
- "<%= (net === 'mainnet')?'8332:8332':'18332:18332' %>"
<% } %>
# deploy:
# placement:
# constraints: [node.hostname==dev]
volumes:
- "<%= bitcoin_datapath %>:/.bitcoin"
networks:
- cyphernodenet
restart: always
<% } %>
broker:
image: eclipse-mosquitto:1.6
# deploy:
# placement:
# constraints: [node.hostname==dev]
networks:
- cyphernodenet
restart: always
notifier:
image: cyphernode/notifier:<%= notifier_version %>
command: $USER ./startnotifier.sh
# deploy:
# placement:
# constraints: [node.hostname==dev]
networks:
- cyphernodenet
- cyphernodeappsnet
restart: always
networks:
cyphernodenet:
external: true
cyphernodeappsnet:
external: true

View File

@@ -0,0 +1,130 @@
#!/bin/sh
. ./installer/config.sh
# be aware that randomly downloaded cyphernode apps will have access to
# your configuration and filesystem.
# !!!!!!!!! DO NOT INCLUDE APPS WITHOUT REVIEW !!!!!!!!!!
# TODO: Test if we can mitigate this security issue by
# running app dockers inside a docker container
start_apps() {
local SCRIPT_NAME="start.sh"
local APP_SCRIPT_PATH
local APP_START_SCRIPT_PATH
local APP_ID
for i in $current_path/apps/*
do
APP_SCRIPT_PATH=$(echo $i)
if [ -d "$APP_SCRIPT_PATH" ] && [ ! -f "$APP_SCRIPT_PATH/ignoreThisApp" ]; then
APP_START_SCRIPT_PATH="$APP_SCRIPT_PATH/$SCRIPT_NAME"
APP_ID=$(basename $APP_SCRIPT_PATH)
if [ -f "$APP_START_SCRIPT_PATH" ]; then
. $APP_START_SCRIPT_PATH
elif [ -f "$APP_SCRIPT_PATH/docker-compose.yaml" ]; then
export SHARED_HTPASSWD_PATH
export GATEKEEPER_DATAPATH
export LIGHTNING_DATAPATH
export BITCOIN_DATAPATH
export APP_SCRIPT_PATH
export APP_ID
export DOCKER_MODE
if [ "$DOCKER_MODE" = "swarm" ]; then
docker stack deploy -c $APP_SCRIPT_PATH/docker-compose.yaml $APP_ID
elif [ "$DOCKER_MODE" = "compose" ]; then
docker-compose -f $APP_SCRIPT_PATH/docker-compose.yaml up -d --remove-orphans
fi
fi
fi
done
}
test_apps() {
local SCRIPT_NAME="test.sh"
local APP_SCRIPT_PATH
local APP_START_SCRIPT_PATH
local APP_ID
local returncode=0
for i in $current_path/apps/*
do
APP_SCRIPT_PATH=$(echo $i)
if [ -d "$APP_SCRIPT_PATH" ]; then
APP_TEST_SCRIPT_PATH="$APP_SCRIPT_PATH/$SCRIPT_NAME"
if [ -f "$APP_TEST_SCRIPT_PATH" ] && [ ! -f "$APP_SCRIPT_PATH/ignoreThisApp" ]; then
APP_ID=$(basename "$APP_SCRIPT_PATH")
printf "\r\n\e[1;36mTesting $APP_ID... \e[1;0m"
. $APP_TEST_SCRIPT_PATH
local rc=$?
if [ ""$rc -eq "0" ]; then
printf "\e[1;36m$APP_ID rocks!\e[1;0m"
fi
returncode=$(($rc | ${returncode}))
echo ""
fi
fi
done
return $returncode
}
<% if (run_as_different_user) { %>
OS=$(uname -s)
if [ "$OS" = "Darwin" ]; then
printf "\r\n\033[0;91m'Run as another user' feature is not supported on OSX. User <%= default_username %> will be used to run Cyphernode.\033[0m\r\n\r\n"
export USER=$(id -u <%= default_username %>):$(id -g <%= default_username %>)
else
export USER=$(id -u <%= username %>):$(id -g <%= username %>)
fi
<% } else { %>
export USER=$(id -u <%= default_username %>):$(id -g <%= default_username %>)
<% } %>
export ARCH=$(uname -m)
current_path="$(cd "$(dirname "$0")" >/dev/null && pwd)"
<% if (docker_mode == 'swarm') { %>
docker stack deploy -c $current_path/docker-compose.yaml cyphernode
<% } else if(docker_mode == 'compose') { %>
docker-compose -f $current_path/docker-compose.yaml up -d --remove-orphans
<% } %>
start_apps
arch=$(uname -m)
case "${arch}" in arm*)
printf "\r\n\033[1;31mSince we're on a slow RPi, let's give Docker 60 more seconds before performing our tests...\033[0m\r\n\r\n"
sleep 60
;;
esac
# Will test if Cyphernode is fully up and running...
docker run --rm -it -v $current_path/testfeatures.sh:/testfeatures.sh \
-v <%= gatekeeper_datapath %>:/gatekeeper \
-v $current_path:/dist \
--network cyphernodenet eclipse-mosquitto:1.6.2 /testfeatures.sh
if [ -f $current_path/exitStatus.sh ]; then
. $current_path/exitStatus.sh
rm -f $current_path/exitStatus.sh
fi
test_apps
EXIT_STATUS=$(($? | ${EXIT_STATUS}))
printf "\r\n\e[1;32mTests finished.\e[0m\n"
if [ "$EXIT_STATUS" -ne "0" ]; then
printf "\r\n\033[1;31mThere was an error during cyphernode installation. Please see Docker's logs for more information. Run ./stop.sh to stop cyphernode.\r\n\r\n\033[0m"
exit 1
fi
printf "\r\n\033[0;92mDepending on your current location and DNS settings, point your favorite browser to one of the following URLs to access Cyphernode's status page:\r\n"
printf "\r\n"
printf "\033[0;95m<% cns.forEach(cn => { %><%= ('https://' + cn + '/welcome\\r\\n') %><% }) %>\033[0m\r\n"
printf "\033[0;92mUse 'admin' as the username with the configuration password you selected at the beginning of the configuration process.\r\n\r\n\033[0m"

View File

@@ -0,0 +1,58 @@
#!/bin/sh
current_path="$(cd "$(dirname "$0")" >/dev/null && pwd)"
# be aware that randomly downloaded cyphernode apps will have access to
# your configuration and filesystem.
# !!!!!!!!! DO NOT INCLUDE APPS WITHOUT REVIEW !!!!!!!!!!
# TODO: Test if we can mitigate this security issue by
# running app dockers inside a docker container
stop_apps() {
local SCRIPT_NAME="stop.sh"
local APP_SCRIPT_PATH
local APP_START_SCRIPT_PATH
local APP_ID
for i in $current_path/apps/*
do
APP_SCRIPT_PATH=$(echo $i)
if [ -d "$APP_SCRIPT_PATH" ] && [ ! -f "$APP_SCRIPT_PATH/ignoreThisApp" ]; then
APP_STOP_SCRIPT_PATH="$APP_SCRIPT_PATH/$SCRIPT_NAME"
APP_ID=$(basename $APP_SCRIPT_PATH)
if [ -f "$APP_STOP_SCRIPT_PATH" ]; then
. $APP_STOP_SCRIPT_PATH
elif [ -f "$APP_SCRIPT_PATH/docker-compose.yaml" ]; then
export SHARED_HTPASSWD_PATH
export GATEKEEPER_DATAPATH
export LIGHTNING_DATAPATH
export BITCOIN_DATAPATH
export APP_SCRIPT_PATH
export APP_ID
export DOCKER_MODE
if [ "$DOCKER_MODE" = "swarm" ]; then
docker stack rm $APP_ID
elif [ "$DOCKER_MODE" = "compose" ]; then
docker-compose -f $APP_SCRIPT_PATH/docker-compose.yaml down
fi
fi
fi
done
}
. ./installer/config.sh
stop_apps
<% if (docker_mode == 'swarm') { %>
export USER=$(id -u):$(id -g)
export ARCH=$(uname -m)
docker stack rm cyphernode
<% } else if(docker_mode == 'compose') { %>
export USER=$(id -u):$(id -g)
export ARCH=$(uname -m)
docker-compose -f $current_path/docker-compose.yaml down
<% } %>

View File

@@ -0,0 +1,374 @@
#!/bin/sh
apk add --update --no-cache openssl curl jq > /dev/null
. /gatekeeper/keys.properties
checkgatekeeper() {
echo -e "\r\n\e[1;36mTesting Gatekeeper...\e[0;32m" > /dev/console
local rc
local id="001"
local k
eval k='$ukey_'$id
local h64=$(echo "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64)
# Let's test expiration: 1 second in payload, request 2 seconds later
local p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+1))}" | base64)
local s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
local token="$h64.$p64.$s"
echo -e " Sleeping 2 seconds... " > /dev/console
sleep 2
echo " Testing expired request... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/getblockinfo)
[ "${rc}" -ne "403" ] && return 10
# Let's test authentication (signature)
p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64)
s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
token="$h64.$p64.a$s"
echo " Testing bad signature... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/getblockinfo)
[ "${rc}" -ne "403" ] && return 30
# Let's test authorization (action access for groups)
token="$h64.$p64.$s"
echo " Testing watcher trying to do a spender action... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/getbalance)
[ "${rc}" -ne "403" ] && return 40
id="002"
eval k='$ukey_'$id
p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64)
s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
token="$h64.$p64.$s"
echo " Testing spender trying to do an internal action call... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/conf)
[ "${rc}" -ne "403" ] && return 50
id="003"
eval k='$ukey_'$id
p64=$(echo "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64)
s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1)
token="$h64.$p64.$s"
echo " Testing admin trying to do an internal action call... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/conf)
[ "${rc}" -ne "403" ] && return 60
echo -e "\e[1;36mGatekeeper rocks!" > /dev/console
return 0
}
checkpycoin() {
echo -en "\r\n\e[1;36mTesting Pycoin... " > /dev/console
local rc
rc=$(curl -H "Content-Type: application/json" -d "{\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/25-30\"}" -s -o /dev/null -w "%{http_code}" http://proxy:8888/derivepubpath)
[ "${rc}" -ne "200" ] && return 100
echo -e "\e[1;36mPycoin rocks!" > /dev/console
return 0
}
checkbroker() {
echo -en "\r\n\e[1;36mTesting Broker... " > /dev/console
local rc
rc=$(mosquitto_pub -h broker -t "testtopic" -m "testbroker")
[ "$?" -ne "0" ] && return 110
echo -e "\e[1;36mBroker rocks!" > /dev/console
return 0
}
checknotifier() {
echo -en "\r\n\e[1;36mTesting Notifier... " > /dev/console
local response
local returncode
response=$(mosquitto_rr -h broker -W 5 -t notifier -e "response/$$" -m "{\"response-topic\":\"response/$$\",\"cmd\":\"web\",\"url\":\"http://proxy:8888/getbestblockhash\"}")
returncode=$?
[ "${returncode}" -ne "0" ] && return 115
http_code=$(echo "${response}" | jq ".http_code" | tr -d '"')
[ "${http_code}" -ge "400" ] && return 118
echo -e "\e[1;36mNotifier rocks!" > /dev/console
return 0
}
checkots() {
echo -en "\r\n\e[1;36mTesting OTSclient... " > /dev/console
local rc
rc=$(curl -s -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' http://proxy:8888/ots_stamp)
echo "${rc}" | grep "Invalid hash 123 for sha256" > /dev/null
[ "$?" -ne "0" ] && return 200
echo -e "\e[1;36mOTSclient rocks!" > /dev/console
return 0
}
checkbitcoinnode() {
echo -en "\r\n\e[1;36mTesting Bitcoin... " > /dev/console
local rc
rc=$(curl -s -o /dev/null -w "%{http_code}" http://proxy:8888/getbestblockhash)
[ "${rc}" -ne "200" ] && return 300
echo -e "\e[1;36mBitcoin node rocks!" > /dev/console
return 0
}
checklnnode() {
echo -en "\r\n\e[1;36mTesting Lightning... " > /dev/console
local rc
rc=$(curl -s -o /dev/null -w "%{http_code}" http://proxy:8888/ln_getinfo)
[ "${rc}" -ne "200" ] && return 400
echo -e "\e[1;36mLN node rocks!" > /dev/console
return 0
}
checkservice() {
local interval=15
local totaltime=180
local outcome
local returncode=0
local endtime=$(($(date +%s) + ${totaltime}))
local result
echo -e "\r\n\e[1;36mTesting if Cyphernode is up and running... \e[0;36mI will keep trying during up to $((${totaltime} / 60)) minutes to give time to Docker to deploy everything...\e[0;32m" > /dev/console
while :
do
outcome=0
for container in gatekeeper proxy proxycron broker notifier pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
echo -e " \e[0;32mVerifying \e[0;33m${container}\e[0;32m..." > /dev/console
(ping -c 10 ${container} 2> /dev/null | grep "0% packet loss" > /dev/null) &
eval ${container}=$!
done
for container in gatekeeper proxy proxycron broker notifier pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
eval wait '$'${container} ; returncode=$? ; outcome=$((${outcome} + ${returncode}))
eval c_${container}=${returncode}
done
# If '0% packet loss' everywhere or 5 minutes passed, we get out of this loop
([ "${outcome}" -eq "0" ] || [ $(date +%s) -gt ${endtime} ]) && break
echo -e "\e[1;31mCyphernode still not ready, will retry every ${interval} seconds for $((${totaltime} / 60)) minutes ($((${endtime} - $(date +%s))) seconds left)." > /dev/console
sleep ${interval}
done
# "containers": [
# { "name": "gatekeeper", "active":true },
# { "name": "proxy", "active":true },
# { "name": "proxycron", "active":true },
# { "name": "pycoin", "active":true },
# { "name": "otsclient", "active":true },
# { "name": "bitcoin", "active":true },
# { "name": "lightning", "active":true },
# ]
for container in gatekeeper proxy proxycron broker notifier pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
[ -n "${result}" ] && result="${result},"
result="${result}{\"name\":\"${container}\",\"active\":"
eval "returncode=\$c_${container}"
if [ "${returncode}" -eq "0" ]; then
result="${result}true}"
else
result="${result}false}"
fi
done
result="\"containers\":[${result}]"
echo $result
return ${outcome}
}
timeout_feature() {
local interval=15
local totaltime=120
local testwhat=${1}
local returncode
local endtime=$(($(date +%s) + ${totaltime}))
while :
do
eval ${testwhat}
returncode=$?
# If no error or 2 minutes passed, we get out of this loop
([ "${returncode}" -eq "0" ] || [ $(date +%s) -gt ${endtime} ]) && break
echo -e "\e[1;31mMaybe it's too early, I'll retry every ${interval} seconds for $((${totaltime} / 60)) minutes ($((${endtime} - $(date +%s))) seconds left)." > /dev/console
sleep ${interval}
done
return ${returncode}
}
feature_status() {
local returncode=${1}
local errormsg=${2}
[ "${returncode}" -eq "0" ] && echo "true"
[ "${returncode}" -ne "0" ] && echo "false" && echo -e "\e[1;31m${errormsg}" > /dev/console
}
# /proxy/installation.json will contain something like that:
#{
# "containers": [
# { "name": "gatekeeper", "active":true },
# { "name": "proxy", "active":true },
# { "name": "proxycron", "active":true },
# { "name": "pycoin", "active":true },
# { "name": "otsclient", "active":true },
# { "name": "bitcoin", "active":true },
# { "name": "lightning", "active":true },
# ],
# "features": [
# { "name": "gatekeeper", "working":true },
# { "name": "pycoin", "working":true },
# { "name": "otsclient", "working":true },
# { "name": "bitcoin", "working":true },
# { "name": "lightning", "working":true },
# ]
#}
# Let's first see if everything is up.
echo "EXIT_STATUS=1" > /dist/exitStatus.sh
brokenproxy="false"
containers=$(checkservice)
returncode=$?
finalreturncode=${returncode}
if [ "${returncode}" -ne "0" ]; then
echo -e "\e[1;31mCyphernode could not fully start properly within delay." > /dev/console
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"proxy\") | .active")
if [ "${status}" = "false" ]; then
echo -e "\e[1;31mThe Proxy, the main Cyphernode's component, is not responding. We will only test the gatekeeper if its container is up, but you'll see errors for the other components. Please check the logs." > /dev/console
brokenproxy="true"
fi
else
echo -e "\e[1;36mCyphernode seems to be correctly deployed. Let's run more thourough tests..." > /dev/console
fi
# Let's now check each feature fonctionality...
# "features": [
# { "name": "gatekeeper", "working":true },
# { "name": "pycoin", "working":true },
# { "name": "otsclient", "working":true },
# { "name": "bitcoin", "working":true },
# { "name": "lightning", "working":true },
# ]
result="${containers},\"features\":[{\"coreFeature\":true, \"name\":\"cyphernode proxy\",\"working\":true}, {\"coreFeature\":true, \"name\":\"gatekeeper\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"gatekeeper\") | .active")
if [ "${status}" = "true" ]; then
timeout_feature checkgatekeeper
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Gatekeeper error!')}"
result="${result},{\"coreFeature\":true, \"name\":\"bitcoin\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"bitcoin\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
timeout_feature checkbitcoinnode
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Bitcoin error!')}"
result="${result},{\"coreFeature\":true, \"name\":\"broker\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"broker\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
timeout_feature checkbroker
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Broker error!')}"
result="${result},{\"coreFeature\":true, \"name\":\"notifier\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"notifier\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
timeout_feature checknotifier
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Notifier error!')}"
result="${result},{\"coreFeature\":true, \"name\":\"pycoin\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"pycoin\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
timeout_feature checkpycoin
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Pycoin error!')}"
<% if (features.indexOf('otsclient') != -1) { %>
result="${result},{\"coreFeature\":false, \"name\":\"otsclient\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"otsclient\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
timeout_feature checkots
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'OTSclient error!')}"
<% } %>
<% if (features.indexOf('lightning') != -1) { %>
result="${result},{\"coreFeature\":false, \"name\":\"lightning\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"lightning\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
timeout_feature checklnnode
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Lightning error!')}"
<% } %>
result="{${result}]}"
echo "${result}" > /gatekeeper/installation.json
echo "EXIT_STATUS=${finalreturncode}" > /dist/exitStatus.sh

View File

@@ -0,0 +1,5 @@
# How to create the hmac for the cookie file:
```
# echo -n "access-key" | openssl dgst -hmac "cyphernode:sparkwallet" -sha256 -binary | base64 | sed 's/[\+\W]//g'
```

View File

@@ -0,0 +1,8 @@
<% if (net === 'testnet') { %>
# testnet
testnet=1
<% } %>
rpcconnect=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>
rpcuser=<%= bitcoin_rpcuser %>
rpcpassword=<%= bitcoin_rpcpassword %>

View File

@@ -0,0 +1,18 @@
<% if (net === 'testnet') { %>
# testnet
network=testnet
<% } else if (net === 'mainnet') { %>
network=bitcoin
<% } %>
<% if( lightning_nodename ) { %>
alias=<%= lightning_nodename %>
<% } %>
<% if( lightning_nodecolor ) { %>
rgb=<%= lightning_nodecolor %>
<% } %>
bitcoin-rpcconnect=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>
bitcoin-rpcuser=<%= bitcoin_rpcuser %>
bitcoin-rpcpassword=<%= bitcoin_rpcpassword %>
addr=0.0.0.0:9735
announce-addr=<%= lightning_external_ip %>:9735

View File

@@ -0,0 +1 @@
cyphernode:sparkwallet:FoeDdQw5yl7pPfqdlGy3OEk/txGqyJjSbVtffhzs7kc=

View File

@@ -0,0 +1,27 @@
[Application Options]
debuglevel=info
maxpendingchannels=10
externalip=88.198.55.131
color=#a111ff
alias=SatoshiPortal01
rpclisten=0.0.0.0:10009
tlsextraip=lnd
tlsextradomain=lnd
[Bitcoin]
bitcoin.active=1
bitcoin.node=bitcoind
bitcoin.mainnet=1
[Bitcoind]
bitcoind.rpcuser=<%= bitcoin_rpcuser %>
bitcoind.rpcpass=<%= bitcoin_rpcpassword %>
bitcoind.zmqpubrawblock=tcp://bitcoin:18501
bitcoind.zmqpubrawtx=tcp://bitcoin:18502
#bitcoind.zmqpath=tcp://bitcoin:18501
bitcoind.rpchost=bitcoin
[autopilot]
autopilot.active=1
autopilot.maxchannels=5
autopilot.allocation=0.6

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
admin:<%- adminhash %>

View File

@@ -0,0 +1,31 @@
debug = false
logLevel = "ERROR"
defaultEntryPoints = ["https","http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[retry]
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "cyphernode.localhost"
watch = true
exposedByDefault = false
[acme]
email = "letsencrypt@yourdomain.com"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]]
main = "cyphernode.yourdomain.com"

View File

@@ -0,0 +1,42 @@
const ApiKey = require('../lib/apikey.js');
test( 'Create ApiKey instance', ()=>{
const apiKey = new ApiKey('testId',['group1','group2']);
expect( apiKey ).not.toBe( undefined );
expect( apiKey.id ).toEqual( 'testId' );
expect( apiKey.groups ).toEqual( ['group1','group2'] );
expect( apiKey.key ).toBe( undefined );
expect( apiKey.script ).toEqual( 'eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}' );
});
test( 'Create ApiKey instance and randomise it', async ()=>{
const apiKey = new ApiKey('testId',['group1','group2']);
await apiKey.randomiseKey();
expect( apiKey ).not.toBe( undefined );
expect( apiKey.id ).toEqual( 'testId' );
expect( apiKey.groups ).toEqual( ['group1','group2'] );
expect( apiKey.key ).not.toBe( undefined );
expect( apiKey.script ).toEqual( 'eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}' );
});
test( 'Create ApiKey instance, randomise it and use getters', async ()=>{
const apiKey = new ApiKey('testId',['group1','group2']);
await apiKey.randomiseKey();
const keyString = apiKey.getKey();
const script = apiKey.script;
expect( keyString ).not.toBe( undefined );
expect( apiKey.id ).toEqual( 'testId' );
expect( apiKey.getClientInformation() ).toEqual( 'testId='+keyString );
expect( apiKey.getConfigEntry() ).toEqual( `kapi_id="testId";kapi_key="${keyString}";kapi_groups="group1,group2";${script}` );
});
test( 'Set properties of ApiKey instance from config entry', async () => {
const configEntry = 'kapi_id="000";kapi_key="b1fdc782037609f8ecc063ac192e92d57544263a950c637ed6b7d79cc9eb9f95";kapi_groups="stats";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}';
const apiKey = new ApiKey();
apiKey.setFromConfigEntry(configEntry);
expect( apiKey.id ).toEqual('000');
expect( apiKey.groups ).toEqual(['stats']);
expect( apiKey.key ).toEqual('b1fdc782037609f8ecc063ac192e92d57544263a950c637ed6b7d79cc9eb9f95');
expect( apiKey.script ).toEqual('eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}')
})

View File

@@ -0,0 +1,26 @@
const Archive = require('../lib/archive.js');
const tmp = require('tmp');
const path = require('path');
test( 'Create Archive instance', ()=>{
new Archive( '/tmp/testArchive.7z', 'test123' );
});
test( 'Write, Read, Delete', async ()=>{
const tmpDir = tmp.dirSync();
const archive = new Archive( path.join(tmpDir.name,'archive.7z'), 'test123' );
await archive.writeEntry('testEntry', 'testContent' );
const c0 = await archive.readEntry( 'testEntry' );
expect( c0.value ).toEqual( 'testContent' );
await archive.deleteEntry('testEntry');
const c1 = await archive.readEntry( 'testEntry' );
expect( c1.value ).toBe( '' );
tmpDir.removeCallback();
});

View File

@@ -0,0 +1,60 @@
const Cert = require('../lib/cert.js');
test( 'Create Cert instance', ()=>{
const cert = new Cert();
expect( cert.args.days ).toBe( 3650 );
});
test( 'buildConfig', ()=>{
const cert = new Cert();
const conf = cert.buildConfig(['127.0.0.1','localhost','gatekeeper']);
expect( conf ).toEqual(`
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no
[req_distinguished_name]
CN = localhost
[v3_ca]
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = gatekeeper
IP.1 = 127.0.0.1
`);
});
test( 'cns', () => {
const cert = new Cert();
const cns = cert.cns(' abc, cde' );
expect( cns ).toEqual([
'127.0.0.1',
'localhost',
'gatekeeper',
'abc',
'cde'
]);
});
test( 'create', async ()=>{
jest.setTimeout(999999);
const cert = new Cert();
const cns = cert.cns('abc,cde' );
const r = await cert.create( cns );
expect( r.code ).toBe(0);
expect( r.key ).not.toBe(undefined);
expect( r.cert ).not.toBe(undefined);
});
test( 'create throws', async ()=>{
const cert = new Cert();
let err;
try {
await cert.create();
} catch( e ) {
err = e;
}
expect( err ).not.toBe(undefined);
});

View File

@@ -0,0 +1,84 @@
const Config = require('../lib/config.js');
const ApiKey = require('../lib/apikey.js');
const fs = require('fs');
const configV010 = require('./data/config.v1.json');
const configV020 = require('./data/config.v2.json');
test( 'create config v0.1.0', () => {
const config = new Config(configV010);
});
test( 'create config v0.2.0', () => {
const config = new Config(configV020);
});
test( 'validate config v0.1.0', () => {
const config = new Config(configV010);
config.data.foo = "bar";
config.data.bar = "foo";
config.validate();
expect( config.data.foo ).toBe( undefined );
expect( config.data.bar ).toBe( undefined );
});
test( 'validate config v0.2.0', () => {
const config = new Config(configV020);
config.data.foo = "bar";
config.data.bar = "foo";
config.validate();
expect( config.data.foo ).toBe( undefined );
expect( config.data.bar ).toBe( undefined );
});
test( 'serialise', async () => {
const config = new Config();
config.setData( configV020 )
await config.serialize('/tmp/config.7z','test123' );
const exists = fs.existsSync('/tmp/config.7z' );
expect( exists ).toBe( true );
});
test( 'deserialise', async () => {
const config = new Config();
await config.deserialize('/tmp/config.7z','test123' );
expect( config.data ).toEqual( configV020 );
});
test( 'migrate 0.1.0 -> 0.2.0', async () => {
const config = new Config();
config.setData( configV010 );
// deep clone gatekeeper_keys
const gatekeeper_keys = JSON.parse(JSON.stringify(configV010.gatekeeper_keys));
await config.migrate_0_1_0_to_0_2_0();
expect( config.data.gatekeeper_keys.configEntries.length ).toBe( 4 );
for( let i=0; i<3; i++ ) {
const configEntry = config.data.gatekeeper_keys.configEntries[i+1];
const oldConfigEntry = gatekeeper_keys.configEntries[i];
const key = new ApiKey();
key.setFromConfigEntry( configEntry )
const oldKey = new ApiKey();
oldKey.setFromConfigEntry( oldConfigEntry );
expect( key.id ).toEqual( oldKey.id );
expect( key.key ).toEqual( oldKey.key );
expect( key.script ).toEqual( oldKey.script );
for( let oldGroup of oldKey.groups ) {
expect( key.groups ).toContain(oldGroup);
}
expect( key.groups ).toContain('stats');
}
});

View File

@@ -0,0 +1,52 @@
{
"__version": "0.1.0",
"features": [
"lightning",
"otsclient"
],
"net": "testnet",
"use_xpub": true,
"installer_mode": "docker",
"run_as_different_user": true,
"username": "cyphernode",
"docker_mode": "compose",
"bitcoin_rpcuser": "bitcoin",
"bitcoin_rpcpassword": "test123",
"bitcoin_uacomment": "",
"bitcoin_prune": true,
"bitcoin_prune_size": 550,
"bitcoin_node_ip": "123.123.123.123",
"bitcoin_datapath": "/Users/jash/.cyphernode/bitcoin",
"bitcoin_mode": "internal",
"bitcoin_expose": false,
"lightning_expose": true,
"gatekeeper_apiproperties": "GKAP",
"gatekeeper_keys": {
"configEntries": [
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
],
"clientInformation": [
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
},
"gatekeeper_sslcert": "-----BEGIN CERTIFICATE-----\nMIIE/jCCAuagAwIBAgIJAIBv4aiI2NRtMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNV\nBAMME2Rpc2swYm9vay5mcml0ei5ib3gwHhcNMTkwMTE3MTcwMDA5WhcNMjkwMTE0\nMTcwMDA5WjAeMRwwGgYDVQQDDBNkaXNrMGJvb2suZnJpdHouYm94MIICIjANBgkq\nhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyVKNTGlPfd4QX9HaDc9a6prbc9il4jtR\niChSlMf3/6UfAMcS+xVgR2iR8FK/DQuFzxn+6BybpoiD333rjDr7zR7y9px3Xph2\nbmsjZy0hv9SIBbx0DJvvwODTlWTAH8qgU2DN6xWc7vjgeGi5uTpnmwWrkH6BjtVr\nwoBkF0JmfH7KiLS/QjWqPKeI6o/GpvCP9meD131Sq/ReoOTrJ4F5aNdhAril4nU5\n6e7Y+Iyp35DZSLuU+pDJAhxEvkYGas1ted5RRxlho8ukaoABCbmaTeNmgsJxK2SC\nABjfUc38aAlNLuMbMMR7Q85Z84OTJiUqanVczwdSj1QHlNCWZK1McBPhj2m2Wdge\ngedrq5XcjQGChzTEozcFntU0qzY3ja1+DOE8UaMaTrDH4saUXCMZk3W1m5mmiZW3\nmcB0cKGdeg6K6USg1BwBTU9qolUusxz5T0tNxjcMlXU93P17d4s5IXfliXhMNr/6\n4fl78Ey3FNprTix4alW7hBAp/eA/LhS55s3jwdoVzJl4RELC0284pahj5exYQwU6\nzjLedMxzC+7veQYwWfZOs9jVCTP0YStuT0j9xD3ausLZyB1Egbsajyy71IeoYOf1\n9S6dFIXE5LHAw2j3D3bh5wb019I8V5szGbeemdBpb3m+bzT8qjLSNranuT41CIHd\nIYjq85vDEJsCAwEAAaM/MD0wOwYDVR0RBDQwMoITZGlzazBib29rLmZyaXR6LmJv\neIIJbG9jYWxob3N0ggpnYXRla2VlcGVyhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IC\nAQBrE4bJsIMwSRPng94PcqR5F6Cux0bkwezALJCHpjHTuqok/wHHE5dZsAXcSsYc\n5givuBESih6CpY5h21Od0TBugyv3FCRY8OoaBXtlO6FYlEnVeJ8AOexJTb3qcbBS\nHU8MBWEydUh5HFA3PRKAG0Y4cvUK4WXJZ42Et3td0NkGFOv6bxdtVGB4Vz7FGn+3\nqd9fpmFCdQYDp6RSZDDz4B8XLsVuTeTES5GbUMSQAGanP7jxMr04wQ3MuoZrRODN\nFatifOJfq0fZddsBjJbrTLxArIqaPh3J4xzwiNE5du4CQDQrbbHXG22kuvbr5foA\ncixLnuyWMq0a5a70mSNS6TZ3nq4ATXNNa0cZ8fBxHqHGTLM8gQisW8vTaZfIFh/i\nhnFcGxtpo1ryi7JG9HCWsh0x20677iag5MuZfv2s4TbK71Ol6WV4FravCqU0qgbn\nTTl+BnYw3H67FO/a6RD4ISlFWK+8EVEQdMgvPoRuw323YznT0Nd8Q/Gq8raYF2wa\nz9T9OXu6TcVGtfPAgX+AM/+hDqWGxyiFR9ZtLpGOHGP8f+TZA5uCawc8Zry4yN6L\nE0yPIx96pJz59T3k8XbRHTQCaPsSUGRAZIY9LpJj0fIG7zCr9eCBpp2qyzmpyNfx\negN3ILYy1Y8JbJj73HWyP0F3Am7i76tkCWB7tQeFOb5FMg==\n-----END CERTIFICATE-----\n",
"gatekeeper_sslkey": "-----BEGIN PRIVATE KEY-----\n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\nmPN+iWirFH2RN5KkSaTzEtrXAgbLvic9\n-----END PRIVATE KEY-----\n",
"gatekeeper_cns": "disk0book.fritz.box",
"proxy_datapath": "/Users/jash/.cyphernode/proxy",
"lightning_implementation": "c-lightning",
"lightning_datapath": "/Users/jash/.cyphernode/lightning",
"lightning_nodename": "🚀 Disciplined Dormouse 🚀",
"lightning_nodecolor": "ff0000",
"otsclient_datapath": "/Users/jash/.cyphernode/otsclient",
"adminhash": "BsJFlh7q4JmwI\n",
"use_xpub": true,
"xpub": "abc",
"derivation_path": "da",
"gatekeeper_clientkeyspassword": "test123",
"gatekeeper_datapath": "/Users/jash/.cyphernode/gatekeeper",
"traefik_datapath": "foo"
}

View File

@@ -0,0 +1,52 @@
{
"__version": "0.2.0",
"features": [
"lightning",
"otsclient"
],
"net": "testnet",
"use_xpub": true,
"installer_mode": "docker",
"run_as_different_user": true,
"username": "cyphernode",
"docker_mode": "compose",
"bitcoin_rpcuser": "bitcoin",
"bitcoin_rpcpassword": "test123",
"bitcoin_uacomment": "",
"bitcoin_prune": true,
"bitcoin_prune_size": 550,
"bitcoin_datapath": "/Users/jash/.cyphernode/bitcoin",
"bitcoin_mode": "internal",
"bitcoin_expose": false,
"lightning_expose": true,
"gatekeeper_keys": {
"configEntries": [
"kapi_id=\"000\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
],
"clientInformation": [
"000=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
},
"gatekeeper_sslcert": "-----BEGIN CERTIFICATE-----\nMIIE/jCCAuagAwIBAgIJAIBv4aiI2NRtMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNV\nBAMME2Rpc2swYm9vay5mcml0ei5ib3gwHhcNMTkwMTE3MTcwMDA5WhcNMjkwMTE0\nMTcwMDA5WjAeMRwwGgYDVQQDDBNkaXNrMGJvb2suZnJpdHouYm94MIICIjANBgkq\nhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyVKNTGlPfd4QX9HaDc9a6prbc9il4jtR\niChSlMf3/6UfAMcS+xVgR2iR8FK/DQuFzxn+6BybpoiD333rjDr7zR7y9px3Xph2\nbmsjZy0hv9SIBbx0DJvvwODTlWTAH8qgU2DN6xWc7vjgeGi5uTpnmwWrkH6BjtVr\nwoBkF0JmfH7KiLS/QjWqPKeI6o/GpvCP9meD131Sq/ReoOTrJ4F5aNdhAril4nU5\n6e7Y+Iyp35DZSLuU+pDJAhxEvkYGas1ted5RRxlho8ukaoABCbmaTeNmgsJxK2SC\nABjfUc38aAlNLuMbMMR7Q85Z84OTJiUqanVczwdSj1QHlNCWZK1McBPhj2m2Wdge\ngedrq5XcjQGChzTEozcFntU0qzY3ja1+DOE8UaMaTrDH4saUXCMZk3W1m5mmiZW3\nmcB0cKGdeg6K6USg1BwBTU9qolUusxz5T0tNxjcMlXU93P17d4s5IXfliXhMNr/6\n4fl78Ey3FNprTix4alW7hBAp/eA/LhS55s3jwdoVzJl4RELC0284pahj5exYQwU6\nzjLedMxzC+7veQYwWfZOs9jVCTP0YStuT0j9xD3ausLZyB1Egbsajyy71IeoYOf1\n9S6dFIXE5LHAw2j3D3bh5wb019I8V5szGbeemdBpb3m+bzT8qjLSNranuT41CIHd\nIYjq85vDEJsCAwEAAaM/MD0wOwYDVR0RBDQwMoITZGlzazBib29rLmZyaXR6LmJv\neIIJbG9jYWxob3N0ggpnYXRla2VlcGVyhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IC\nAQBrE4bJsIMwSRPng94PcqR5F6Cux0bkwezALJCHpjHTuqok/wHHE5dZsAXcSsYc\n5givuBESih6CpY5h21Od0TBugyv3FCRY8OoaBXtlO6FYlEnVeJ8AOexJTb3qcbBS\nHU8MBWEydUh5HFA3PRKAG0Y4cvUK4WXJZ42Et3td0NkGFOv6bxdtVGB4Vz7FGn+3\nqd9fpmFCdQYDp6RSZDDz4B8XLsVuTeTES5GbUMSQAGanP7jxMr04wQ3MuoZrRODN\nFatifOJfq0fZddsBjJbrTLxArIqaPh3J4xzwiNE5du4CQDQrbbHXG22kuvbr5foA\ncixLnuyWMq0a5a70mSNS6TZ3nq4ATXNNa0cZ8fBxHqHGTLM8gQisW8vTaZfIFh/i\nhnFcGxtpo1ryi7JG9HCWsh0x20677iag5MuZfv2s4TbK71Ol6WV4FravCqU0qgbn\nTTl+BnYw3H67FO/a6RD4ISlFWK+8EVEQdMgvPoRuw323YznT0Nd8Q/Gq8raYF2wa\nz9T9OXu6TcVGtfPAgX+AM/+hDqWGxyiFR9ZtLpGOHGP8f+TZA5uCawc8Zry4yN6L\nE0yPIx96pJz59T3k8XbRHTQCaPsSUGRAZIY9LpJj0fIG7zCr9eCBpp2qyzmpyNfx\negN3ILYy1Y8JbJj73HWyP0F3Am7i76tkCWB7tQeFOb5FMg==\n-----END CERTIFICATE-----\n",
"gatekeeper_sslkey": "-----BEGIN PRIVATE KEY-----\n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\nmPN+iWirFH2RN5KkSaTzEtrXAgbLvic9\n-----END PRIVATE KEY-----\n",
"gatekeeper_cns": "disk0book.fritz.box",
"proxy_datapath": "/Users/jash/.cyphernode/proxy",
"lightning_implementation": "c-lightning",
"lightning_datapath": "/Users/jash/.cyphernode/lightning",
"lightning_nodename": "🚀 Disciplined Dormouse 🚀",
"lightning_nodecolor": "ff0000",
"otsclient_datapath": "/Users/jash/.cyphernode/otsclient",
"adminhash": "BsJFlh7q4JmwI\n",
"use_xpub": true,
"xpub": "abc",
"derivation_path": "da",
"gatekeeper_clientkeyspassword": "test123",
"gatekeeper_datapath": "/Users/jash/.cyphernode/gatekeeper",
"traefik_datapath": "foo"
}

View File

@@ -0,0 +1,6 @@
const htpasswd = require('../lib/htpasswd.js');
test( 'generate htpasswd', async ()=>{
const pw = await htpasswd( 'test123' );
expect( pw ).not.toBe( undefined );
});

View File

@@ -0,0 +1,7 @@
const name = require('../lib/name.js');
test( 'Create new random name', ()=>{
const n = name.generate();
expect( n ).not.toBe( undefined )
});

44
dist/setup.sh vendored
View File

@@ -139,7 +139,7 @@ configure() {
local recreate=""
if [[ $1 == 1 ]]; then
recreate="recreate"
recreate=" recreate"
fi
@@ -151,8 +151,6 @@ configure() {
if [[ -t 1 ]]; then
interactive=' -it'
else
gen_options=' --force 2'
fi
if [[ $CFG_PASSWORD ]]; then
@@ -186,7 +184,6 @@ configure() {
-e DEFAULT_DATADIR_BASE=$HOME \
-e SETUP_DIR=$SETUP_DIR \
-e DEFAULT_CERT_HOSTNAME=$(hostname) \
-e VERSION_OVERRIDE=$VERSION_OVERRIDE \
-e GATEKEEPER_VERSION=$GATEKEEPER_VERSION \
-e PROXY_VERSION=$PROXY_VERSION \
-e NOTIFIER_VERSION=$NOTIFIER_VERSION \
@@ -195,9 +192,10 @@ configure() {
-e PYCOIN_VERSION=$PYCOIN_VERSION \
-e BITCOIN_VERSION=$BITCOIN_VERSION \
-e LIGHTNING_VERSION=$LIGHTNING_VERSION \
-e SETUP_VERSION=$SETUP_VERSION \
--log-driver=none$pw_env \
--network none \
--rm$interactive cyphernode/cyphernodeconf:$CONF_VERSION $user yo --no-insight cyphernode$gen_options $recreate
--rm$interactive cyphernode/cyphernodeconf:$CONF_VERSION $user node index.js$recreate
if [[ -f $current_path/exitStatus.sh ]]; then
. $current_path/exitStatus.sh
rm $current_path/exitStatus.sh
@@ -701,7 +699,6 @@ install() {
fi
}
CONFIGURE=0
INSTALL=0
RECREATE=0
@@ -710,8 +707,8 @@ ALWAYSYES=0
SUDO_REQUIRED=0
AUTOSTART=0
# CYPHERNODE VERSION "v0.1.1"
VERSION_OVERRIDE="true"
# CYPHERNODE VERSION "v0.2.0"
SETUP_VERSION="v0.2.0"
CONF_VERSION="v0.2.0"
GATEKEEPER_VERSION="v0.2.0"
PROXY_VERSION="v0.2.0"
@@ -734,31 +731,6 @@ function ctrl_c() {
export current_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
#***************************************************************
# Temporary code for upgrading from v0.1 to v0.2
#***************************************************************
grep "xpub" gatekeeper/api.properties > /dev/null
returncode=$?
if [[ $returncode -eq 1 ]]; then
# grep found the file but didn't find xpub in it
echo "\nPrevious Cyphernode installation detected."
echo "Running migration scripts...\n"
echo "You will be asked to enter your admin passphrase twice while migrating. It is the passphrase you used when installing previous verison of Cyphernode.\n"
# We want to add the 000 KEY_ID (Stats) and update the api.properties file with new endpoints
docker run --rm -it -v "$SETUP_DIR:/conf" alpine:3.8 sh -c "apk add --no-cache --update curl ; curl -fsSL https://raw.githubusercontent.com/SatoshiPortal/cyphernode/${GATEKEEPER_VERSION}/api_auth_docker/api-sample.properties -o /conf/api-sample.properties"
docker run --rm -it -v "$SETUP_DIR:/conf" alpine:3.8 sh -c 'apk add --no-cache --update jq p7zip;apk add --no-cache --update jq curl p7zip;cd conf;7z e config.7z;k=$(dd if=/dev/urandom bs=32 count=1 2> /dev/null | xxd -pc 32) && l="kapi_id=\\\"000\\\";kapi_key=\\\"$k\\\";kapi_groups=\\\"stats\\\";eval ugroups_\${kapi_id}=\${kapi_groups};eval ukey_\${kapi_id}=\${kapi_key}" && cat config.json | sed 's/kapi_groups=\\"/kapi_groups=\\"stats,/g' | jq ".gatekeeper_keys.configEntries = [\"$l\"] + .gatekeeper_keys.configEntries" | jq ".gatekeeper_keys.clientInformation = [\"000=$k\"] + .gatekeeper_keys.clientInformation" | jq ".gatekeeper_apiproperties = \"$(cat api-sample.properties | paste -s -d '\\\\n')\"" > config.json;7z u config.7z config.json;'
fi
#***************************************************************
# Temporary code for upgrading from v0.1 to v0.2
#***************************************************************
while getopts ":cirhys" opt; do
case $opt in
r)
@@ -832,10 +804,12 @@ if [[ $INSTALL == 1 ]]; then
modify_owner
modify_permissions
install_apps
if [[ ! $AUTOSTART == 1 ]]; then
cowsay
fi
fi
if [[ $AUTOSTART == 1 ]]; then
exec $current_path/start.sh
else
cowsay
fi