328 lines
9.7 KiB
JavaScript
328 lines
9.7 KiB
JavaScript
/* jslint node: true */
|
|
/* eslint-disable no-console */
|
|
'use strict';
|
|
|
|
// ENiGMA½
|
|
const resolvePath = require('../../core/misc_util.js').resolvePath;
|
|
const {
|
|
printUsageAndSetExitCode,
|
|
getConfigPath,
|
|
argv,
|
|
ExitCodes,
|
|
getAnswers,
|
|
writeConfig,
|
|
HJSONStringifyCommonOpts,
|
|
} = require('./oputil_common.js');
|
|
const getHelpFor = require('./oputil_help.js').getHelpFor;
|
|
|
|
// deps
|
|
const async = require('async');
|
|
const inq = require('inquirer');
|
|
const mkdirsSync = require('fs-extra').mkdirsSync;
|
|
const fs = require('graceful-fs');
|
|
const hjson = require('hjson');
|
|
const paths = require('path');
|
|
const _ = require('lodash');
|
|
const sanatizeFilename = require('sanitize-filename');
|
|
|
|
exports.handleConfigCommand = handleConfigCommand;
|
|
|
|
const ConfigIncludeKeys = [
|
|
'theme',
|
|
'users.preAuthIdleLogoutSeconds',
|
|
'users.idleLogoutSeconds',
|
|
'users.newUserNames',
|
|
'users.failedLogin',
|
|
'users.unlockAtEmailPwReset',
|
|
'paths.logs',
|
|
'loginServers',
|
|
'contentServers',
|
|
'fileBase.areaStoragePrefix',
|
|
'logging.rotatingFile',
|
|
];
|
|
|
|
const QUESTIONS = {
|
|
Intro: [
|
|
{
|
|
name: 'createNewConfig',
|
|
message: 'Create a new configuration?',
|
|
type: 'confirm',
|
|
default: false,
|
|
},
|
|
{
|
|
name: 'configPath',
|
|
message: 'Configuration path:',
|
|
default: getConfigPath(),
|
|
when: answers => answers.createNewConfig,
|
|
},
|
|
],
|
|
|
|
OverwriteConfig: [
|
|
{
|
|
name: 'overwriteConfig',
|
|
message: 'Config file exists. Overwrite?',
|
|
type: 'confirm',
|
|
default: false,
|
|
},
|
|
],
|
|
|
|
Basic: [
|
|
{
|
|
name: 'boardName',
|
|
message: 'BBS name:',
|
|
default: 'New ENiGMA½ BBS',
|
|
},
|
|
],
|
|
|
|
Misc: [
|
|
{
|
|
name: 'loggingLevel',
|
|
message: 'Logging level:',
|
|
type: 'list',
|
|
choices: ['Error', 'Warn', 'Info', 'Debug', 'Trace'],
|
|
default: 2,
|
|
filter: s => s.toLowerCase(),
|
|
},
|
|
],
|
|
|
|
MessageConfAndArea: [
|
|
{
|
|
name: 'msgConfName',
|
|
message: 'First message conference:',
|
|
default: 'Local',
|
|
},
|
|
{
|
|
name: 'msgConfDesc',
|
|
message: 'Conference description:',
|
|
default: 'Local Areas',
|
|
},
|
|
{
|
|
name: 'msgAreaName',
|
|
message: 'First area in message conference:',
|
|
default: 'General',
|
|
},
|
|
{
|
|
name: 'msgAreaDesc',
|
|
message: 'Area description:',
|
|
default: 'General chit-chat',
|
|
},
|
|
],
|
|
};
|
|
|
|
function makeMsgConfAreaName(s) {
|
|
return s.toLowerCase().replace(/\s+/g, '_');
|
|
}
|
|
|
|
function askNewConfigQuestions(cb) {
|
|
const ui = new inq.ui.BottomBar();
|
|
|
|
let configPath;
|
|
let config;
|
|
|
|
async.waterfall(
|
|
[
|
|
function intro(callback) {
|
|
getAnswers(QUESTIONS.Intro, answers => {
|
|
if (!answers.createNewConfig) {
|
|
return callback('exit');
|
|
}
|
|
|
|
// adjust for ~ and the like
|
|
configPath = resolvePath(answers.configPath);
|
|
|
|
const configDir = paths.dirname(configPath);
|
|
mkdirsSync(configDir);
|
|
|
|
//
|
|
// Check if the file exists and can be written to
|
|
//
|
|
fs.access(configPath, fs.F_OK | fs.W_OK, err => {
|
|
if (err) {
|
|
if ('EACCES' === err.code) {
|
|
ui.log.write(`${configPath} cannot be written to`);
|
|
callback('exit');
|
|
} else if ('ENOENT' === err.code) {
|
|
callback(null, false);
|
|
}
|
|
} else {
|
|
callback(null, true); // exists + writable
|
|
}
|
|
});
|
|
});
|
|
},
|
|
function promptOverwrite(needPrompt, callback) {
|
|
if (needPrompt) {
|
|
getAnswers(QUESTIONS.OverwriteConfig, answers => {
|
|
return callback(answers.overwriteConfig ? null : 'exit');
|
|
});
|
|
} else {
|
|
return callback(null);
|
|
}
|
|
},
|
|
function basic(callback) {
|
|
getAnswers(QUESTIONS.Basic, answers => {
|
|
const defaultConfig = require('../../core/config_default')();
|
|
|
|
// start by plopping in values we want directly from config.js
|
|
const template = hjson.rt.parse(
|
|
fs.readFileSync(
|
|
paths.join(__dirname, '../../misc/config_template.in.hjson'),
|
|
'utf8'
|
|
)
|
|
);
|
|
|
|
const direct = {};
|
|
_.each(ConfigIncludeKeys, keyPath => {
|
|
_.set(direct, keyPath, _.get(defaultConfig, keyPath));
|
|
});
|
|
|
|
config = _.mergeWith(template, direct);
|
|
|
|
// we can override/add to it based on user input from this point on...
|
|
config.general.boardName = answers.boardName;
|
|
|
|
return callback(null);
|
|
});
|
|
},
|
|
function msgConfAndArea(callback) {
|
|
getAnswers(QUESTIONS.MessageConfAndArea, answers => {
|
|
const confName = makeMsgConfAreaName(answers.msgConfName);
|
|
const areaName = makeMsgConfAreaName(answers.msgAreaName);
|
|
|
|
config.messageConferences[confName] = {
|
|
name: answers.msgConfName,
|
|
desc: answers.msgConfDesc,
|
|
sort: 1,
|
|
default: true,
|
|
};
|
|
|
|
config.messageConferences[confName].areas = {};
|
|
config.messageConferences[confName].areas[areaName] = {
|
|
name: answers.msgAreaName,
|
|
desc: answers.msgAreaDesc,
|
|
sort: 1,
|
|
default: true,
|
|
};
|
|
|
|
return callback(null);
|
|
});
|
|
},
|
|
function misc(callback) {
|
|
getAnswers(QUESTIONS.Misc, answers => {
|
|
config.logging.rotatingFile.level = answers.loggingLevel;
|
|
|
|
return callback(null);
|
|
});
|
|
},
|
|
],
|
|
err => {
|
|
return cb(err, configPath, config);
|
|
}
|
|
);
|
|
}
|
|
|
|
const copyFileSyncSilent = (to, from, flags) => {
|
|
try {
|
|
fs.copyFileSync(to, from, flags);
|
|
} catch (e) {
|
|
/* absorb! */
|
|
}
|
|
};
|
|
|
|
function buildNewConfig() {
|
|
askNewConfigQuestions((err, configPath, config) => {
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
// ensure 'menus' exists
|
|
mkdirsSync(paths.join(__dirname, '../../config/menus'));
|
|
|
|
const boardName = sanatizeFilename(config.general.boardName)
|
|
.replace(/[^a-z0-9_-]/gi, '_')
|
|
.replace(/_+/g, '_')
|
|
.toLowerCase();
|
|
|
|
const includeFilesIn = [
|
|
'message_base.in.hjson',
|
|
'private_mail.in.hjson',
|
|
'login.in.hjson',
|
|
'new_user.in.hjson',
|
|
'doors.in.hjson',
|
|
'file_base.in.hjson',
|
|
'activitypub.in.hjson',
|
|
];
|
|
|
|
let includeFiles = [];
|
|
includeFilesIn.forEach(incFile => {
|
|
const outName = `${boardName}-${incFile.replace('.in', '')}`;
|
|
includeFiles.push(outName);
|
|
|
|
copyFileSyncSilent(
|
|
paths.join(__dirname, '../../misc/menu_templates', incFile),
|
|
paths.join(__dirname, '../../config/menus', outName),
|
|
fs.constants.COPYFILE_EXCL
|
|
);
|
|
});
|
|
|
|
// We really only need includes to be replaced
|
|
const mainTemplate = fs
|
|
.readFileSync(
|
|
paths.join(__dirname, '../../misc/menu_templates/main.in.hjson'),
|
|
'utf8'
|
|
)
|
|
.replace(/%INCLUDE_FILES%/g, includeFiles.join('\n\t\t')); // cheesy, but works!
|
|
|
|
const menuFile = `${boardName}-main.hjson`;
|
|
fs.writeFileSync(
|
|
paths.join(__dirname, '../../config/menus', menuFile),
|
|
mainTemplate,
|
|
'utf8'
|
|
);
|
|
|
|
config.general.menuFile = paths.join(__dirname, '../../config/menus/', menuFile);
|
|
|
|
if (writeConfig(config, configPath)) {
|
|
console.info('Configuration generated');
|
|
} else {
|
|
console.error('Failed writing configuration');
|
|
}
|
|
});
|
|
}
|
|
|
|
function catCurrentConfig() {
|
|
try {
|
|
const config = hjson.rt.parse(fs.readFileSync(getConfigPath(), 'utf8'));
|
|
const hjsonOpts = Object.assign({}, HJSONStringifyCommonOpts, {
|
|
colors: false === argv.colors ? false : true,
|
|
keepWsc: false === argv.comments ? false : true,
|
|
});
|
|
|
|
console.log(hjson.stringify(config, hjsonOpts));
|
|
} catch (e) {
|
|
if ('ENOENT' == e.code) {
|
|
console.error(`File not found: ${getConfigPath()}`);
|
|
} else {
|
|
console.error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleConfigCommand() {
|
|
if (true === argv.help) {
|
|
return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR);
|
|
}
|
|
|
|
const action = argv._[1];
|
|
|
|
switch (action) {
|
|
case 'new':
|
|
return buildNewConfig();
|
|
case 'cat':
|
|
return catCurrentConfig();
|
|
|
|
default:
|
|
return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR);
|
|
}
|
|
}
|