'use strict'; const Generator = require('yeoman-generator'); const chalk = require('chalk'); const wrap = require('wrap-ansi'); const html2ansi = require('./lib/html2ansi.js'); const fs = require('fs'); const validator = require('validator'); const path = require("path"); const coinstring = require('coinstring'); const Archive = require('./lib/archive.js'); const ApiKey = require('./lib/apikey.js'); const featureChoices = require('./features.json'); const uaCommentRegexp = /^[a-zA-Z0-9 \.,:_\-\?\/@]+$/; // TODO: look for spec of unsafe chars const userRegexp = /^[a-zA-Z0-9\._\-]+$/; const reset = '\u001B8\u001B[u'; const clear = '\u001Bc'; const defaultAPIProperties = ` action_watch=watcher action_unwatch=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_getbalance=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_conf=internal action_executecallbacks=internal `; const prefix = function() { return chalk.green('Cyphernode')+': '; }; let prompters = []; fs.readdirSync(path.join(__dirname, "prompters")).forEach(function(file) { prompters.push(require(path.join(__dirname, "prompters",file))); }); const sleep = function(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } const easeOutCubic = function(t, b, c, d) { return c*((t=t/d-1)*t*t+1)+b; } const splash = async function() { let frames = []; fs.readdirSync(path.join(__dirname,'splash')).forEach(function(file) { frames.push(fs.readFileSync(path.join(__dirname,'splash',file))); }); const frame0 = frames[0]; const frame0lines = frame0.toString().split('\n'); const frame0lineCount = frame0lines.length; const steps = 10; process.stdout.write(clear); await sleep(150); for( let i=0; i<=steps; i++ ) { const pos = easeOutCubic( i, 0, frame0lineCount, steps ) | 0; process.stdout.write(reset); for( let l=frame0lineCount-pos; l { this.props = Object.assign(this.props, props); }); } async configuring() { if( this.props.auth_recreatekeys || this.props.auth_keys.configEntries.length===0 ) { delete this.props.auth_recreatekeys; const apikey = new ApiKey(); let configEntries = []; let clientInformation = []; apikey.setId('001'); apikey.setGroups(['watcher']); await apikey.randomiseKey(); configEntries.push(apikey.getConfigEntry()); clientInformation.push(apikey.getClientInformation()); apikey.setId('002'); apikey.setGroups(['watcher','spender']); await apikey.randomiseKey(); configEntries.push(apikey.getConfigEntry()); clientInformation.push(apikey.getClientInformation()); apikey.setId('003'); apikey.setGroups(['watcher','spender','admin']); await apikey.randomiseKey(); configEntries.push(apikey.getConfigEntry()); clientInformation.push(apikey.getClientInformation()); this.props.auth_keys = { configEntries: configEntries, clientInformation: clientInformation } } } async writing() { const configJsonString = JSON.stringify(this.props, null, 4); const archive = new Archive( this.destinationPath('config.7z'), this.configurationPassword ); if( !await archive.writeEntry( 'config.json', configJsonString ) ) { console.log(chalk.bold.red( 'error! Config archive was not written' )); } for( let m of prompters ) { const name = m.name(); for( let t of m.templates(this.props) ) { const p = path.join(name,t); this.fs.copyTpl( this.templatePath(p), this.destinationPath(p), this.props ); } } if( this.props.auth_keys && this.props.auth_keys.clientInformation ) { if( this.auth_clientkeyspassword !== this.props.auth_clientkeyspassword ) { fs.unlinkSync( this.destinationPath('clientKeys.7z') ); } const archive = new Archive( this.destinationPath('clientKeys.7z'), this.props.auth_clientkeyspassword ); if( !await archive.writeEntry( 'keys.txt', this.props.auth_keys.clientInformation.join('\n') ) ) { console.log(chalk.bold.red( 'error! Client auth key archive was not written' )); } } } install() { } /* some utils */ _hasAuthKeys() { return this.props && this.props.auth_keys && this.props.auth_keys.configEntries && this.props.auth_keys.configEntries.length > 0; } _assignConfigDefaults() { this.props = Object.assign( { features: [], enablehelp: true, net: 'testnet', xpub: '', derivation_path: '0/n', installer_mode: 'docker', devmode: false, devregistry: false, username: 'cyphernode', docker_mode: 'compose', bitcoin_rpcuser: 'bitcoin', bitcoin_rpcpassword: 'CHANGEME', bitcoin_uacomment: '', bitcoin_prune: false, bitcoin_datapath: '', bitcoin_node_ip: '', bitcoin_mode: 'internal', bitcoin_expose: false, auth_apiproperties: defaultAPIProperties, auth_ipwhitelist: '', auth_keys: { configEntries: [], clientInformation: [] }, proxy_datapath: '', lightning_implementation: 'c-lightning', lightning_datapath: '', lightning_nodename: '', lightning_nodecolor: '' }, this.props ); } _isChecked( name, value ) { return this.props && this.props[name] && this.props[name].indexOf(value) != -1 ; } _getDefault( name ) { return this.props && this.props[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; } _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.featureChoices; } _getHelp( topic ) { if( !this.props.enablehelp || !this.help ) { return ''; } // TODO: remove default later: const helpText = this.help[topic] || this.help['__default__']; if( !helpText ||helpText === '' ) { return ''; } return "\n\n"+wrap( html2ansi(helpText),82 )+"\n\n"; } };