config wizard

This commit is contained in:
Paul Sori 2018-10-30 20:06:47 -04:00
parent 37ddd09a77
commit 7b4f2bfb47
3 changed files with 352 additions and 45 deletions

View File

@ -2,10 +2,9 @@ const inquirer = require('inquirer');
const colors = require('colors');
const fs = require('fs');
const path = require('path');
const Login = require('../login')
const Login = require('../login');
exports.init = function(filepath, callback) {
console.log(filepath)
function initFile(filepath) {
if (!filepath) {
console.log(colors.yellow('No filepath given'));
return;
@ -13,24 +12,30 @@ exports.init = function(filepath, callback) {
// Check that path exists
if (fs.existsSync(filepath)) {
inquirer
return inquirer
.prompt([{
message: "This file already exists. Do you want to overwrite it?",
message: "This file already exists. Do you want to overwrite it with an empty config?",
type: "confirm",
name: "confirm"
name: "confirm",
default: false
}])
.then(answers => {
if(answers.confirm === true) {
fs.writeFileSync( filepath, JSON.stringify({}), 'utf8');
return callback();
return true;
}
return false;
});
}else {
fs.writeFileSync( filepath, JSON.stringify({}), 'utf8');
callback();
return Promise.resolve(true);
}
}
exports.init = function(filepath) {
return initFile(filepath);
}
exports.makeSecret = function(current, callback) {
if (current.secret) {
ask1();
@ -152,18 +157,13 @@ exports.addCert = function(current, filepath, callback) {
callback(current);
}
exports.editPort = function(current, callback) {
if (current.port) {
console.log(`Port is currently set to ${current.port}`);
} else {
console.log('Port is set to default: 3000');
}
inquirer
function editPort(port = 3000) {
return inquirer
.prompt([{
message: "Port Number (1 - 65535):",
type: "input",
name: "port",
default: port,
validate: answer => {
if (!Number.isInteger(Number(answer)) || Number(answer) < 1 || Number(answer) > 65535) {
return 'Port must be a an integer between 1 and 65535!';
@ -172,11 +172,14 @@ exports.editPort = function(current, callback) {
}
}])
.then(answers => {
current.port = Number(answers.port);
callback(current);
return answers.port;
});
}
exports.editPort = function() {
return editPort();
}
exports.deleteUser = function(current, callback) {
if(!current.users || Object.keys(current.users).length === 0){
console.log(colors.yellow('No users found'));
@ -255,6 +258,86 @@ exports.deleteFolder = function(current, callback) {
});
}
// TODO: LastFM user
function addOneUser(current) {
var paths = [];
Object.keys(current.folders).forEach(key => {
paths.push({ name: key });
});
if (paths.length === 1) {
paths[0].checked = true;
}
var answers;
return inquirer
.prompt([{
message: "Username:",
type: "input",
name: "username",
validate: answer => {
if (answer.length < 1) {
return 'You need a username';
}
// Check that username doesn't already exist
if (current.users && current.users[answer]) {
return 'Username already exists';
}
return true;
}
},
{
message: "Password:",
type: "password",
name: "password",
validate: answer => {
if (answer.length < 1) {
return 'You need a password';
}
return true;
}
},
{
type: 'checkbox',
message: 'Select directories user has access to:',
name: 'vpaths',
choices: paths,
validate: answer => {
if (answer.length < 1) {
return 'You must choose at least one folder.';
}
return true;
}
}])
.then(ans => {
answers = ans;
return hashPassword(answers.password);
})
.then((hashObj) => {
if(!current.users){
current.users = {};
}
current.users[answers.username] = {
vpaths: answers.vpaths,
password: hashObj.hashPassword,
salt: hashObj.salt
};
});
}
function hashPassword(password) {
return new Promise((resolve, reject) => {
Login.hashPassword(password, (salt, hashedPassword, err) => {
if (err) {
// return callback(false, err);
return reject('Failed to hash password');
}
resolve({salt, hashPassword: Buffer.from(hashedPassword).toString('hex')});
});
});
}
exports.addUser = function(current, callback) {
if(!current.folders || Object.keys(current.folders).length === 0){
console.log(colors.yellow('You need to add a folder before you can add a user'));
@ -267,12 +350,6 @@ exports.addUser = function(current, callback) {
paths.push({ name: key });
});
if (paths.length < 1) {
console.log(colors.yellow('You need to add a folder before you can add a user'));
console.log(`Use the ${colors.blue('--addpath')} command to add a folder`);
return;
}
if (paths.length === 1) {
paths[0].checked = true;
}
@ -337,6 +414,41 @@ exports.addUser = function(current, callback) {
});
}
function namePathAlias(current) {
return inquirer
.prompt([{
message: "Path Alias (no spaces or special characters):",
type: "input",
name: "name",
validate: answer => {
if (answer.length < 1) {
return 'Cannot be empty';
}
// Verify inputs
if (!/^([a-z0-9]{1,})$/.test(answer)) {
return 'Name cannot have spaces or special characters';
}
// Check that name doesn't already exist
var keyExists = false;
if (current.folders) {
Object.keys(current.folders).forEach(key => {
if (key === answer){
keyExists = true;
}
});
}
if (keyExists === true) {
return 'This name already exists';
}
return true;
}
}])
.then(answers => {
return answers.name;
});
}
exports.addPath = function(current, filepath, callback) {
if(!filepath){
console.log(colors.yellow('No path given'));
@ -370,9 +482,7 @@ exports.addPath = function(current, filepath, callback) {
if (typeof current.folders[key] === 'object' && current.folders[key].root && current.folders[key].root === filepath) {
exists = key;
}
// TODO: Detect if it's a subpath as well
}
});
}
@ -386,7 +496,7 @@ exports.addPath = function(current, filepath, callback) {
// Ask user for path name
inquirer
.prompt([{
message: "Path Name (no spaces or special characters):",
message: "Path Alias (no spaces or special characters):",
type: "input",
name: "name",
validate: answer => {
@ -423,24 +533,211 @@ exports.addPath = function(current, filepath, callback) {
});
}
exports.wizard = function(file) {
// check if file exists
// Warn user if so
exports.wizard = function(filepath) {
doTheThing(filepath);
}
function confirmThis(confirmText, defaults = false) {
return inquirer
.prompt([{
message: confirmText,
type: "confirm",
name: "confirm",
default: defaults
}])
.then(answers => {
if(answers.confirm === true) {
return true;
}
return false;
});
}
function addNewFolder() {
inquirer.registerPrompt('directory', require('inquirer-select-directory'));
return inquirer.prompt([{
type: 'directory',
name: 'from',
message: 'Choose your music directory:',
basePath: '.'
}]).then((answers) => {
return answers.from;
});
}
async function doTheThing(filepath) {
console.clear();
console.log();
console.log(colors.blue.bold('Welcome To The mStream Setup Wizard'));
console.log(colors.blue.yellow('You can run this wizard at any time to edit your config file'));
console.log();
console.log('You can read more on mStream configuration here:');
console.log(colors.grey.bold.underline('https://irosthebeggar.github.io/mStream/docs/json_config.html'));
console.log();
console.log(`${colors.bold('Config File:')} ${colors.green(filepath)}`);
if (!fs.existsSync(filepath)) {
const didWrite = await confirmThis('Create this file to continue?', true);
if (!didWrite) {
console.clear();
console.log();
console.log('Exiting Setup Wizard...');
console.log();
process.exit();
}
}
await initFile(filepath);
try {
var loadJson = JSON.parse(fs.readFileSync(filepath, 'utf8'));
} catch (error) {
console.log();
console.log("ERROR: Failed to parse JSON file");
console.log();
console.log('Exiting Setup Wizard...');
console.log();
process.exit();
}
console.clear();
console.log();
console.log(colors.blue.bold('Welcome To The mStream Setup Wizard'));
console.log(colors.magenta('Directory Configuration'));
console.log();
if (loadJson.folders && typeof loadJson.folders === 'object') {
printDirs(loadJson.folders);
} else {
loadJson.folders = {};
}
var forceAdd = false;
if (Object.keys(loadJson.folders).length === 0) {
forceAdd = true;
}
if (!forceAdd) {
forceAdd = await confirmThis("Would you like to add another directory?");
}
while (forceAdd) {
const newDir = await addNewFolder();
const folderAlias = await namePathAlias(loadJson);
loadJson.folders[folderAlias] = { root: newDir };
console.clear();
console.log();
console.log(colors.blue.bold('Welcome To The mStream Setup Wizard'));
console.log(colors.magenta('Directory Configuration'));
console.log();
printDirs(loadJson.folders);
forceAdd = await confirmThis("Would you like to add a new directory?");
}
console.clear();
console.log();
console.log(colors.blue.bold('Welcome To The mStream Setup Wizard'));
console.log(colors.magenta('User Configuration'));
console.log();
var addUser = false;
if (!loadJson.users || typeof loadJson.users !== 'object') {
loadJson.users = {};
}
if (Object.keys(loadJson.users).length === 0) {
console.log('There are currently no users');
console.log(colors.yellow('With no users, mStream will be publicly available and the login system will be disabled'));
console.log();
} else {
printUsers(loadJson.users);
}
addUser = await confirmThis("Would you like to add a user?");
// Add folders
while (addUser) {
await addOneUser(loadJson);
// Add users
// LastFM scrobbler
console.clear();
console.log();
console.log(colors.blue.bold('Welcome To The mStream Setup Wizard'));
console.log(colors.magenta('User Configuration'));
console.log();
printUsers(loadJson.users);
// Generate Hash
addUser = await confirmThis("Would you like to add a user?");
}
// Setup Port
console.clear();
console.log();
console.log(colors.blue.bold('Welcome To The mStream Setup Wizard'));
console.log(colors.magenta('Set Port'));
console.log();
loadJson.port = await editPort(loadJson.port);
// Secret
if (!loadJson.secret) {
loadJson.secret = await generateSecret();
} else {
console.clear();
console.log();
console.log(colors.blue.bold('Welcome To The mStream Setup Wizard'));
console.log(colors.magenta('Secret Generator'));
console.log();
console.log('The Secret Key is used to secure login sessions');
console.log('Generating a new secret will force all users to sign in again');
console.log();
const shouldMakeNewSecret = await confirmThis("You already have a secret. Would you like to make a new one?");
if (shouldMakeNewSecret) {
loadJson.secret = await generateSecret();
}
}
// Database Location
// Save
fs.writeFileSync( filepath, JSON.stringify(loadJson, null, 2), 'utf8');
console.clear();
console.log();
console.log(colors.blue.bold('Welcome To The mStream Setup Wizard'));
console.log(colors.magenta('Config Saved!'));
console.log();
console.log(colors.bold('You can start mStream by running the command:'));
console.log(`mstream -j ${filepath}`);
console.log();
// Print a Help Text explaining basic usage things
}
// Prompt to boot server now
}
function generateSecret() {
return new Promise((resolve, reject) => {
require('crypto').randomBytes(48, function (err, buffer) {
if (err) {
reject();
}
resolve(buffer.toString('hex'));
});
});
}
function printDirs(folders) {
console.log('Your config has the following folders:');
Object.entries(folders).forEach(([key, value]) => {
var thisDir;
if (typeof value === 'string' || value instanceof String) {
thisDir = value;
} else {
thisDir = value.root;
}
console.log(`${colors.green.bold.dim(key)}: ${thisDir}`);
});
console.log();
}
function printUsers(users) {
console.log('Your config has the following users:');
Object.entries(users).forEach(([key, value]) => {
console.log(` * ${colors.green.bold.dim(key)}`);
});
console.log();
}

View File

@ -41,7 +41,7 @@ exports.setup = function (args) {
.option('-P, --dbpause <dbpause>', 'Specify File Scan Pause Interval', /^\d+$/i, 0)
// Logs
.option('-L, --logs <path>', 'Specify Database Filepath', './logs/mstream.db')
.option('-L, --logs <path>', 'Specify Database Filepath')
// JSON config
.option('-j, --json <json>', 'Specify JSON Boot File')
@ -56,16 +56,24 @@ exports.setup = function (args) {
.option("--makesecret", "Add an SSL Cert")
.option("--removeuser", "Delete User From Config")
.option("--removepath", "Remove Folder From Config")
.option("--wizard <file>", "Setup Wizard")
.parse(args);
if (program.init) {
require('./config-inquirer').init(program.init, () => {
console.log(colors.green(`Created ${program.init}!`));
require('./config-inquirer').init(program.init).then((didWrite) => {
if (didWrite) {
console.log(colors.green(`Created ${program.init}!`));
}
});
return false;
}
if (program['wizard']) {
require('./config-inquirer').wizard(program.wizard);
return false;
}
// Use JSON config
if (program.json) {
try {
@ -97,8 +105,9 @@ exports.setup = function (args) {
}
if (program['editport']) {
require('./config-inquirer').editPort(loadJson, modJson => {
fs.writeFileSync( program.json, JSON.stringify(modJson, null, 2), 'utf8');
require('./config-inquirer').editPort().then(port => {
loadJson.port = port;
fs.writeFileSync( program.json, JSON.stringify(loadJson, null, 2), 'utf8');
console.log(colors.green('Port Updated!'));
});
return false;
@ -140,7 +149,7 @@ exports.setup = function (args) {
if (program['removepath']) {
require('./config-inquirer').deleteFolder(loadJson, modJson => {
fs.writeFileSync( program.json, JSON.stringify(modJson, null, 2), 'utf8');
console.log(colors.green('User Deleted'));
console.log(colors.green('Path Deleted'));
});
return false;
}

View File

@ -26,6 +26,7 @@
"ffbinaries": "^1.0.9",
"fluent-ffmpeg": "^2.1.2",
"inquirer": "^6.2.0",
"inquirer-select-directory": "^1.2.0",
"internal-ip": "^3.0.0",
"jsonwebtoken": "^8.3.0",
"lokijs": "^1.5.5",