diff --git a/lib/cli/commands/action/create.js b/lib/cli/commands/action/create.js index 204cb0e..1ec27bd 100644 --- a/lib/cli/commands/action/create.js +++ b/lib/cli/commands/action/create.js @@ -1,103 +1,103 @@ -const templates = require(`../../../templates`); -const path = require(`path`); -const fs = require(`fs`); -const chalk = require(`chalk`); -const commandLineArgs = require('command-line-args'); -const config = require(`../../../config`); - -const options = [ - { - name: 'help', - alias: 'h', - description: 'Shows this help message.', - type: Boolean - }, - { - name: 'name', - alias: 'n', - description: 'The name for the action.', - defaultOption: true - }, - { - name: 'receiver', - alias: 'r', - description: 'Create a receiver instead of an action.', - type: Boolean - }, - { - name: 'instance', - alias: 'i', - description: 'The path to the local Coattail Instance.', - typeLabel: '{underline path}' - } -]; - -const printHelp = cli => cli.printHelp([ - { - header: 'Coattail -- Create Action', - content: 'Creates a new action on this Coattail instance.' - }, - { - header: 'Usage', - content: [ - `$ coattail action create [options]` - ] - }, - { - header: 'Options', - optionList: options - } -]); - -module.exports = async cli => { - const parameters = commandLineArgs(options, { - argv: cli.argv, - stopAtFirstUnknown: true - }); - - if (parameters.help) { - printHelp(cli); - return; - } - - if(!cli.validateRequired(parameters, ['name'])) { - return; - } - - try { - config.load(parameters.instance); - } catch (_) { - cli.error(`Unable to locate 'config.yml' in '${parameters.instance || './'}'`); - return; - } - - if (!parameters.receiver) { - const destination = path.join(config.get().paths.actions, `${parameters.name}.js`); - - if (fs.existsSync(destination)) { - cli.error(`An action with that name already exists.`, `Found in ${chalk.hex('#4e88e6')(destination)}`); - return; - } - - try { - templates.create('action', destination, 'js'); - cli.success(`Action ${parameters.name}.js created! (${chalk.hex('#4e88e6')(destination)})`); - } catch (err) { - cli.error(`Failed to create action ${destination}.`, err.stack) - } - } else { - const destination = path.join(config.get().paths.receivers, `${parameters.name}.js`); - - if (fs.existsSync(destination)) { - cli.error(`A receiver with that name already exists.`, `Found in ${chalk.hex('#4e88e6')(destination)}`); - return; - } - - try { - templates.create('receiver', destination, 'js'); - cli.success(`Receiver ${parameters.name}.js created! (${chalk.hex('#4e88e6')(destination)})`); - } catch (err) { - cli.error(`Failed to create receiver ${destination}.`, err.stack) - } - } +const templates = require(`../../../templates`); +const path = require(`path`); +const fs = require(`fs`); +const chalk = require(`chalk`); +const commandLineArgs = require('command-line-args'); +const config = require(`../../../config`); + +const options = [ + { + name: 'help', + alias: 'h', + description: 'Shows this help message.', + type: Boolean + }, + { + name: 'name', + alias: 'n', + description: 'The name for the action.', + defaultOption: true + }, + { + name: 'receiver', + alias: 'r', + description: 'Create a receiver instead of an action.', + type: Boolean + }, + { + name: 'instance', + alias: 'i', + description: 'The path to the local Coattail Instance.', + typeLabel: '{underline path}' + } +]; + +const printHelp = cli => cli.printHelp([ + { + header: 'Coattail -- Create Action', + content: 'Creates a new action on this Coattail instance.' + }, + { + header: 'Usage', + content: [ + `$ coattail action create [options]` + ] + }, + { + header: 'Options', + optionList: options + } +]); + +module.exports = async cli => { + const parameters = commandLineArgs(options, { + argv: cli.argv, + stopAtFirstUnknown: true + }); + + if (parameters.help) { + printHelp(cli); + return; + } + + if(!cli.validateRequired(parameters, ['name'])) { + return; + } + + try { + config.load(parameters.instance); + } catch (_) { + cli.error(`Unable to locate 'config.yml' in '${parameters.instance || process.cwd()}'`); + return; + } + + if (!parameters.receiver) { + const destination = path.join(config.get().paths.actions, `${parameters.name}.js`); + + if (fs.existsSync(destination)) { + cli.error(`An action with that name already exists.`, `Found in ${chalk.hex('#4e88e6')(destination)}`); + return; + } + + try { + templates.create('action', destination, 'js'); + cli.success(`Action ${parameters.name}.js created! (${chalk.hex('#4e88e6')(destination)})`); + } catch (err) { + cli.error(`Failed to create action ${destination}.`, err.stack) + } + } else { + const destination = path.join(config.get().paths.receivers, `${parameters.name}.js`); + + if (fs.existsSync(destination)) { + cli.error(`A receiver with that name already exists.`, `Found in ${chalk.hex('#4e88e6')(destination)}`); + return; + } + + try { + templates.create('receiver', destination, 'js'); + cli.success(`Receiver ${parameters.name}.js created! (${chalk.hex('#4e88e6')(destination)})`); + } catch (err) { + cli.error(`Failed to create receiver ${destination}.`, err.stack) + } + } }; \ No newline at end of file diff --git a/lib/cli/new.js b/lib/cli/new.js index cd37c94..6e69417 100644 --- a/lib/cli/new.js +++ b/lib/cli/new.js @@ -1,218 +1,227 @@ -const templates = require(`../templates`); -const path = require(`path`); -const fs = require(`fs`); -const chalk = require(`chalk`); -const { generateKeyPairSync } = require(`crypto`); -const commandLineArgs = require(`command-line-args`); - -const options = [ - { - name: 'help', - alias: 'h', - description: 'Shows this help message.', - type: Boolean - }, - { - name: 'dir', - alias: 'd', - description: 'The directory in which to place the Coattail instance. Defaults to the current directory.', - defaultOption: true - } -]; - -const printHelp = cli => cli.printHelp([ - { - header: 'Coattail -- New', - content: 'Creates a new Coattail instance.' - }, - { - header: 'Usage', - content: [ - `$ coattail new [options]` - ] - }, - { - header: 'Options', - optionList: options - } -]); - -module.exports = async cli => { - const parameters = commandLineArgs(options, { - argv: cli.argv, - stopAtFirstUnknown: true - }); - - if (parameters.help) { - printHelp(cli); - return; - } - - let directory = process.cwd(); - if (parameters.dir) { - directory = path.resolve(parameters.dir); - } - - if (!fs.existsSync(directory)) { - cli.error(`Directory '${chalk.hex('#e6d74e')(directory)}' does not exist.`); - return; - } - - if(!fs.lstatSync(directory).isDirectory()) { - cli.error(`Path '${chalk.hex('#e6d74e')(directory)}' is not a directory.`); - return; - } - - if(fs.readdirSync(directory).length > 0) { - cli.error(`Directory '${chalk.hex('#e6d74e')(directory)}' is not empty.`); - return; - } - - const configPath = path.join(directory, 'config.yml'); - const actionsPath = path.join(directory, 'actions'); - const receiversPath = path.join(directory, 'receivers'); - const dbPath = path.join(directory, 'data.db'); - const keysPath = path.join(directory, 'keys'); - const logPath = path.join(directory, 'service.log'); - const packagePath = path.join(directory, 'package.json'); - const authPrivateKeyPath = path.join(keysPath, 'auth-key.pem'); - const authPublicKeyPath = path.join(keysPath, 'auth-key.pub'); - const vtPrivateKeyPath = path.join(keysPath, 'vt-key.pem'); - const vtPublicKeyPath = path.join(keysPath, 'vt-key.pub'); - - - cli.waiting(`Creating new Coattail Instance in ${chalk.hex('#4e88e6')(directory)}...`) - - const lines = []; - - const printFailed = (action, err) => { - try { - const files = fs.readdirSync(directory); - for (const file of files) { - fs.unlinkSync(path.join(directory, file)); - } - } catch (_) {} - let stack = `${action}\n\n${err.stack || err}`; - cli.error(`Error while generating Coattail Instance`, stack); - }; - - try { - templates.create('package', packagePath, 'json'); - lines.push(`${chalk.hex('#4e88e6')(packagePath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(packagePath), err); - return; - } - - try { - templates.create('config', configPath, 'yml', { - '{root}': directory, - '{dbfile}': dbPath, - '{install}': path.resolve(path.join(__dirname, '..', '..')) - }); - lines.push(`${chalk.hex('#4e88e6')(configPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(configPath), err); - return; - } - - try { - fs.mkdirSync(actionsPath); - lines.push(`${chalk.hex('#4e88e6')(actionsPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(actionsPath), err); - return; - } - - try { - fs.mkdirSync(receiversPath); - lines.push(`${chalk.hex('#4e88e6')(receiversPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(receiversPath), err); - return; - } - - try { - fs.closeSync(fs.openSync(logPath, 'w')); - lines.push(`${chalk.hex('#4e88e6')(logPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(logPath), err); - return; - } - - let authKeys; - let vtKeys; - try { - fs.mkdirSync(keysPath); - - authKeys = generateKeyPairSync('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem' - } - }); - - vtKeys = generateKeyPairSync('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem' - } - }); - lines.push(`${chalk.hex('#4e88e6')(keysPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(keysPath), err); - return; - } - - try { - fs.writeFileSync(authPublicKeyPath, authKeys.publicKey); - lines.push(`${chalk.hex('#4e88e6')(authPublicKeyPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(authPublicKeyPath), err); - return; - } - - try { - fs.writeFileSync(authPrivateKeyPath, authKeys.privateKey); - lines.push(`${chalk.hex('#4e88e6')(authPrivateKeyPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(authPrivateKeyPath), err); - return; - } - - try { - fs.writeFileSync(vtPublicKeyPath, vtKeys.publicKey); - lines.push(`${chalk.hex('#4e88e6')(vtPublicKeyPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(vtPublicKeyPath), err); - return; - } - - try { - fs.writeFileSync(vtPrivateKeyPath, vtKeys.privateKey); - lines.push(`${chalk.hex('#4e88e6')(vtPrivateKeyPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(vtPrivateKeyPath), err); - return; - } - - try { - fs.closeSync(fs.openSync(dbPath, 'w')); - lines.push(`${chalk.hex('#4e88e6')(dbPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); - } catch (err) { - printFailed(chalk.hex('#4e88e6')(dbPath), err); - return; - } - - cli.success(`Coattail Instance Created`, lines); +const templates = require(`../templates`); +const path = require(`path`); +const fs = require(`fs`); +const chalk = require(`chalk`); +const { generateKeyPairSync } = require(`crypto`); +const commandLineArgs = require(`command-line-args`); + +const options = [ + { + name: 'help', + alias: 'h', + description: 'Shows this help message.', + type: Boolean + }, + { + name: 'dir', + alias: 'd', + description: 'The directory in which to place the Coattail instance. Defaults to the current directory.', + defaultOption: true + } +]; + +const printHelp = cli => cli.printHelp([ + { + header: 'Coattail -- New', + content: 'Creates a new Coattail instance.' + }, + { + header: 'Usage', + content: [ + `$ coattail new [options]` + ] + }, + { + header: 'Options', + optionList: options + } +]); + +module.exports = async cli => { + const parameters = commandLineArgs(options, { + argv: cli.argv, + stopAtFirstUnknown: true + }); + + if (parameters.help) { + printHelp(cli); + return; + } + + let directory = process.cwd(); + if (parameters.dir) { + directory = path.resolve(parameters.dir); + } + + if (!fs.existsSync(directory)) { + cli.error(`Directory '${chalk.hex('#e6d74e')(directory)}' does not exist.`); + return; + } + + if(!fs.lstatSync(directory).isDirectory()) { + cli.error(`Path '${chalk.hex('#e6d74e')(directory)}' is not a directory.`); + return; + } + + if(fs.readdirSync(directory).length > 0) { + cli.error(`Directory '${chalk.hex('#e6d74e')(directory)}' is not empty.`); + return; + } + + const configPath = path.join(directory, 'config.yml'); + const actionsPath = path.join(directory, 'actions'); + const receiversPath = path.join(directory, 'receivers'); + const dbPath = path.join(directory, 'data.db'); + const keysPath = path.join(directory, 'keys'); + const logPath = path.join(directory, 'service.log'); + const packagePath = path.join(directory, 'package.json'); + const install = path.resolve(path.join(__dirname, '..', '..')); + const authPrivateKeyPath = path.join(keysPath, 'auth-key.pem'); + const authPublicKeyPath = path.join(keysPath, 'auth-key.pub'); + const vtPrivateKeyPath = path.join(keysPath, 'vt-key.pem'); + const vtPublicKeyPath = path.join(keysPath, 'vt-key.pub'); + + + cli.waiting(`Creating new Coattail Instance in ${chalk.hex('#4e88e6')(directory)}...`) + + const lines = []; + + const printFailed = (action, err) => { + try { + const files = fs.readdirSync(directory); + for (const file of files) { + fs.unlinkSync(path.join(directory, file)); + } + } catch (_) {} + let stack = `${action}\n\n${err.stack || err}`; + cli.error(`Error while generating Coattail Instance`, stack); + }; + + try { + templates.create('package', packagePath, 'json'); + lines.push(`${chalk.hex('#4e88e6')(packagePath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(packagePath), err); + return; + } + + try { + templates.create('config', configPath, 'yml', { + '{root}': directory, + '{dbfile}': dbPath, + '{install}': install, + '{actions}': path.join(directory, 'actions'), + '{receivers}': path.join(directory, 'receivers'), + '{log}': logPath, + '{migrations}': path.join(install, 'lib', 'data', 'migrations'), + '{auth-key-priv}': authPrivateKeyPath, + '{auth-key-pub}': authPublicKeyPath, + '{validation-key-priv}': vtPrivateKeyPath, + '{validation-key-pub}': vtPublicKeyPath + }); + lines.push(`${chalk.hex('#4e88e6')(configPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(configPath), err); + return; + } + + try { + fs.mkdirSync(actionsPath); + lines.push(`${chalk.hex('#4e88e6')(actionsPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(actionsPath), err); + return; + } + + try { + fs.mkdirSync(receiversPath); + lines.push(`${chalk.hex('#4e88e6')(receiversPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(receiversPath), err); + return; + } + + try { + fs.closeSync(fs.openSync(logPath, 'w')); + lines.push(`${chalk.hex('#4e88e6')(logPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(logPath), err); + return; + } + + let authKeys; + let vtKeys; + try { + fs.mkdirSync(keysPath); + + authKeys = generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }); + + vtKeys = generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }); + lines.push(`${chalk.hex('#4e88e6')(keysPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(keysPath), err); + return; + } + + try { + fs.writeFileSync(authPublicKeyPath, authKeys.publicKey); + lines.push(`${chalk.hex('#4e88e6')(authPublicKeyPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(authPublicKeyPath), err); + return; + } + + try { + fs.writeFileSync(authPrivateKeyPath, authKeys.privateKey); + lines.push(`${chalk.hex('#4e88e6')(authPrivateKeyPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(authPrivateKeyPath), err); + return; + } + + try { + fs.writeFileSync(vtPublicKeyPath, vtKeys.publicKey); + lines.push(`${chalk.hex('#4e88e6')(vtPublicKeyPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(vtPublicKeyPath), err); + return; + } + + try { + fs.writeFileSync(vtPrivateKeyPath, vtKeys.privateKey); + lines.push(`${chalk.hex('#4e88e6')(vtPrivateKeyPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(vtPrivateKeyPath), err); + return; + } + + try { + fs.closeSync(fs.openSync(dbPath, 'w')); + lines.push(`${chalk.hex('#4e88e6')(dbPath)} : ${chalk.hex('#6ce64e')(`Success!`)}`); + } catch (err) { + printFailed(chalk.hex('#4e88e6')(dbPath), err); + return; + } + + cli.success(`Coattail Instance Created`, lines); }; \ No newline at end of file diff --git a/lib/config.js b/lib/config.js index 8082033..66cc95c 100644 --- a/lib/config.js +++ b/lib/config.js @@ -4,7 +4,7 @@ const yaml = require(`js-yaml`); let config; module.exports = { - load: function (location = './') { + load: function (location = process.cwd()) { const contents = fs.readFileSync(path.join(location, 'config.yml'), 'utf8'); const res = yaml.load(contents); config = res; diff --git a/templates/config.template.yml b/templates/config.template.yml index 7a99f0d..0c84190 100644 --- a/templates/config.template.yml +++ b/templates/config.template.yml @@ -1,47 +1,47 @@ -paths: - root: "{root}" - actions: "{root}/actions/" - receivers: "{root}/receivers/" - -service: - # TLS Configuration - tls: - enabled: false - key: '/absolute/path/to/key.pem' - cert: '/absolute/path/to/cert.pem' - # The local address and port to bind to. - network: - address: - # The address that other peers should use to connect to this instance. - connection: "127.0.0.1" - # The address that this peer should bind to when setting up it's server. - bind: "127.0.0.1" - # The port to use. - port: 49365 - # The session ID factory to use, can be either `uuid` or `incremental`. - session_id_factory: 'uuid' - log: "{root}/service.log" - -authentication: - private_key: - type: "file" - value: "{root}/keys/auth-key.pem" - public_key: - type: "file" - value: "{root}/keys/auth-key.pub" - -validation: - private_key: - type: "file" - value: "{root}/keys/vt-key.pem" - public_key: - type: "file" - value: "{root}/keys/vt-key.pub" - -data: - client: "sqlite3" - connection: - filename: "{dbfile}" - migrations: - directory: "{install}/lib/data/migrations" - tableName: "knex_migrations" +paths: + root: {root} + actions: {actions} + receivers: {receivers} + +service: + # TLS Configuration + tls: + enabled: false + key: /absolute/path/to/key.pem + cert: /absolute/path/to/cert.pem + # The local address and port to bind to. + network: + address: + # The address that other peers should use to connect to this instance. + connection: "127.0.0.1" + # The address that this peer should bind to when setting up it's server. + bind: "127.0.0.1" + # The port to use. + port: 49365 + # The session ID factory to use, can be either `uuid` or `incremental`. + session_id_factory: uuid + log: {log} + +authentication: + private_key: + type: file + value: {auth-key-priv} + public_key: + type: file + value: {auth-key-pub} + +validation: + private_key: + type: file + value: {validation-key-priv} + public_key: + type: file + value: {validation-key-pub} + +data: + client: sqlite3 + connection: + filename: {dbfile} + migrations: + directory: {migrations} + tableName: knex_migrations