diff --git a/install/Dockerfile b/install/Dockerfile index 98f944f..7dbf845 100644 --- a/install/Dockerfile +++ b/install/Dockerfile @@ -1,6 +1,6 @@ FROM node:alpine -RUN apk add --update bash su-exec && rm -rf /var/cache/apk/* +RUN apk add --update bash su-exec p7zip && rm -rf /var/cache/apk/* RUN mkdir -p /app RUN mkdir /.config RUN chmod a+rwx /.config diff --git a/install/generator-cyphernode/generators/app/index.js b/install/generator-cyphernode/generators/app/index.js index ff02fcb..727ec00 100644 --- a/install/generator-cyphernode/generators/app/index.js +++ b/install/generator-cyphernode/generators/app/index.js @@ -7,6 +7,7 @@ const validator = require('validator'); const path = require("path"); const featureChoices = require(path.join(__dirname, "features.json")); const coinstring = require('coinstring'); +const Archive = require('./lib/archive.js'); const uaCommentRegexp = /^[a-zA-Z0-9 \.,:_\-\?\/@]+$/; // TODO: look for spec of unsafe chars const userRegexp = /^[a-zA-Z0-9\._\-]+$/; @@ -73,28 +74,87 @@ module.exports = class extends Generator { this.recreate = true; } - if( fs.existsSync(this.destinationPath('config.json')) ) { - this.props = require(this.destinationPath('config.json')); - } else { - this.props = {}; - } - - this.props.derivation_path = this.props.derivation_path || '0/n'; - this.props.installer = this.props.installer ||  'docker'; - this.props.devmode = this.props.devmode || false; - this.props.devregistry = this.props.devregistry || false; - this.props.devmode = this.props.devmode || false; - this.props.username = this.props.username || 'cyphernode'; - this.featureChoices = featureChoices; - for( let c of this.featureChoices ) { - c.checked = this._isChecked( 'features', c.value ); - } } + async _initConfig() { + if( fs.existsSync(this.destinationPath('config.7z')) ) { + let r = {}; + while( !r.password ) { + r = await this.prompt([{ + type: 'password', + name: 'password', + message: chalk.bold.blue('Enter your configuration password?'), + filter: this._trimFilter + }]); + } + + this.configurationPassword = r.password; + + const archive = new Archive( this.destinationPath('config.7z'), this.configurationPassword ); + + r = await archive.readEntry('config.json'); + + if( r.error ) { + console.log(chalk.bold.red('Password is wrong. Have a nice day.')); + process.exit(1); + } + + if( !r.value ) { + console.log(chalk.bold.red('config archive is corrupt.')); + process.exit(1); + } + + try { + this.props = JSON.parse(r.value); + } catch( err ) { + console.log(chalk.bold.red('config archive is corrupt.')); + process.exit(1); + } + + this._assignConfigDefaults(this.props); + + for( let c of this.featureChoices ) { + c.checked = this._isChecked( 'features', c.value ); + } + + } else { + let r = {}; + 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: chalk.bold.blue('Choose your configuration password'), + filter: this._trimFilter + }, + { + type: 'password', + name: 'password1', + message: chalk.bold.blue('Confirm your configuration password'), + filter: this._trimFilter + }]); + } + + this.configurationPassword = r.password0; + this.props = {}; + this._assignConfigDefaults(this.props); + + console.log(chalk.bold.green('Password is set')); + + } + } + async prompting() { + process.stdout.write(reset); + await this._initConfig(); + await sleep(1000); await splash(); if( this.recreate ) { @@ -113,7 +173,14 @@ module.exports = class extends Generator { } writing() { - fs.writeFileSync(this.destinationPath('config.json'), JSON.stringify(this.props, null, 2)); + const configJsonString = JSON.stringify(this.props); + const archive = new Archive( this.destinationPath('config.7z'), this.configurationPassword ); + + if( archive.writeEntry( 'config.json', configJsonString ) ) { + console.log(chalk.bold.green( 'config archive was written' )); + } else { + console.log(chalk.bold.red( 'error! config archive was not written' )); + } for( let m of prompters ) { const name = m.name(); @@ -132,6 +199,16 @@ module.exports = class extends Generator { } /* some utils */ + + _assignConfigDefaults( props ) { + props.derivation_path = this.props.derivation_path || '0/n'; + props.installer = this.props.installer ||  'docker'; + props.devmode = this.props.devmode || false; + props.devregistry = this.props.devregistry || false; + props.devmode = this.props.devmode || false; + props.username = this.props.username || 'cyphernode'; + } + _isChecked( name, value ) { return this.props && this.props[name] && this.props[name].indexOf(value) != -1 ; } diff --git a/install/generator-cyphernode/generators/app/lib/archive.js b/install/generator-cyphernode/generators/app/lib/archive.js new file mode 100644 index 0000000..4795d89 --- /dev/null +++ b/install/generator-cyphernode/generators/app/lib/archive.js @@ -0,0 +1,77 @@ +const fs = require('fs'); +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; + } + +} diff --git a/install/generator-cyphernode/package.json b/install/generator-cyphernode/package.json index 634a27a..0fd77c9 100644 --- a/install/generator-cyphernode/package.json +++ b/install/generator-cyphernode/package.json @@ -20,6 +20,7 @@ "npm": ">= 4.0.0" }, "dependencies": { + "@rauschma/stringio": "^1.4.0", "chalk": "^2.1.0", "coinstring": "^2.3.0", "validator": "^10.8.0",