From 2c4fdfdd5f427ecddf47dd6329dfcc997c05e3c8 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 24 Nov 2018 20:02:19 -0700 Subject: [PATCH] * Moved oputil.js config import-areas to 'oputil.js mb import-areas' * 'oputil.js mb import-areas' now *optionally* binds areas to FTN networks. Otherwise only areas are imported --- core/bbs.js | 2 +- core/oputil/oputil_common.js | 35 +++ core/oputil/oputil_config.js | 339 +---------------------------- core/oputil/oputil_help.js | 18 +- core/oputil/oputil_message_base.js | 324 ++++++++++++++++++++++++++- docs/admin/oputil.md | 42 +++- 6 files changed, 407 insertions(+), 353 deletions(-) diff --git a/core/bbs.js b/core/bbs.js index 05168e72..ede2b82e 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -216,7 +216,7 @@ function initialize(cb) { }, function loadSysOpInformation(callback) { // - // Copy over some +op information from the user DB -> system propertys. + // Copy over some +op information from the user DB -> system properties. // * Makes this accessible for MCI codes, easy non-blocking access, etc. // * We do this every time as the op is free to change this information just // like any other user diff --git a/core/oputil/oputil_common.js b/core/oputil/oputil_common.js index 90328b07..e1d6c962 100644 --- a/core/oputil/oputil_common.js +++ b/core/oputil/oputil_common.js @@ -7,6 +7,11 @@ const db = require('../../core/database.js'); const _ = require('lodash'); const async = require('async'); +const inq = require('inquirer'); +const fs = require('fs'); +const hjson = require('hjson'); + +const packageJson = require('../../package.json'); exports.printUsageAndSetExitCode = printUsageAndSetExitCode; exports.getDefaultConfigPath = getDefaultConfigPath; @@ -14,6 +19,17 @@ exports.getConfigPath = getConfigPath; exports.initConfigAndDatabases = initConfigAndDatabases; exports.getAreaAndStorage = getAreaAndStorage; exports.looksLikePattern = looksLikePattern; +exports.getAnswers = getAnswers; +exports.writeConfig = writeConfig; + +const HJSONStringifyCommonOpts = exports.HJSONStringifyCommonOpts = { + emitRootBraces : true, + bracesSameLine : true, + space : 4, + keepWsc : true, + quotes : 'min', + eol : '\n', +}; const exitCodes = exports.ExitCodes = { SUCCESS : 0, @@ -100,4 +116,23 @@ function looksLikePattern(tag) { } return /[*?[\]!()+|^]/.test(tag); +} + +function getAnswers(questions, cb) { + inq.prompt(questions).then( answers => { + return cb(answers); + }); +} + +function writeConfig(config, path) { + config = hjson.stringify(config, HJSONStringifyCommonOpts) + .replace(/%ENIG_VERSION%/g, packageJson.version) + .replace(/%HJSON_VERSION%/g, hjson.version); + + try { + fs.writeFileSync(path, config, 'utf8'); + return true; + } catch(e) { + return false; + } } \ No newline at end of file diff --git a/core/oputil/oputil_config.js b/core/oputil/oputil_config.js index 52c388f7..8a51fdb5 100644 --- a/core/oputil/oputil_config.js +++ b/core/oputil/oputil_config.js @@ -9,10 +9,11 @@ const { getConfigPath, argv, ExitCodes, - initConfigAndDatabases + getAnswers, + writeConfig, + HJSONStringifyCommonOpts, } = require('./oputil_common.js'); const getHelpFor = require('./oputil_help.js').getHelpFor; -const Errors = require('../../core/enig_error.js').Errors; // deps const async = require('async'); @@ -24,17 +25,8 @@ const paths = require('path'); const _ = require('lodash'); const sanatizeFilename = require('sanitize-filename'); -const packageJson = require('../../package.json'); - exports.handleConfigCommand = handleConfigCommand; - -function getAnswers(questions, cb) { - inq.prompt(questions).then( answers => { - return cb(answers); - }); -} - const ConfigIncludeKeys = [ 'theme', 'users.preAuthIdleLogoutSeconds', 'users.idleLogoutSeconds', @@ -46,15 +38,6 @@ const ConfigIncludeKeys = [ 'logging.rotatingFile', ]; -const HJSONStringifyComonOpts = { - emitRootBraces : true, - bracesSameLine : true, - space : 4, - keepWsc : true, - quotes : 'min', - eol : '\n', -}; - const QUESTIONS = { Intro : [ { @@ -231,34 +214,21 @@ function askNewConfigQuestions(cb) { ); } -function writeConfig(config, path) { - config = hjson.stringify(config, HJSONStringifyComonOpts) - .replace(/%ENIG_VERSION%/g, packageJson.version) - .replace(/%HJSON_VERSION%/g, hjson.version) - ; - - try { - fs.writeFileSync(path, config, 'utf8'); - return true; - } catch(e) { - return false; - } -} - const copyFileSyncSilent = (to, from, flags) => { try { fs.copyFileSync(to, from, flags); - } catch(e) {} + } catch(e) { + /* absorb! */ + } }; function buildNewConfig() { askNewConfigQuestions( (err, configPath, config) => { - if(err) { - return; + if(err) { return; } const bn = sanatizeFilename(config.general.boardName) - .replace(/[^a-z0-9_\-]/ig, '_') + .replace(/[^a-z0-9_-]/ig, '_') .replace(/_+/g, '_') .toLowerCase(); const menuFile = `${bn}-menu.hjson`; @@ -273,7 +243,7 @@ function buildNewConfig() { paths.join(__dirname, '../../misc/prompt_template.in.hjson'), paths.join(__dirname, '../../config/', promptFile), fs.constants.COPYFILE_EXCL - ) + ); config.general.menuFile = menuFile; config.general.promptFile = promptFile; @@ -286,294 +256,10 @@ function buildNewConfig() { }); } -function validateUplinks(uplinks) { - const ftnAddress = require('../../core/ftn_address.js'); - const valid = uplinks.every(ul => { - const addr = ftnAddress.fromString(ul); - return addr; - }); - return valid; -} - -function getMsgAreaImportType(path) { - if(argv.type) { - return argv.type.toLowerCase(); - } - - const ext = paths.extname(path).toLowerCase().substr(1); - return ext; // .bbs|.na|... -} - -function importAreas() { - const importPath = argv._[argv._.length - 1]; - if(argv._.length < 3 || !importPath || 0 === importPath.length) { - return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR); - } - - const importType = getMsgAreaImportType(importPath); - if('na' !== importType && 'bbs' !== importType) { - return console.error(`"${importType}" is not a recognized import file type`); - } - - // optional data - we'll prompt if for anything not found - let confTag = argv.conf; - let networkName = argv.network; - let uplinks = argv.uplinks; - if(uplinks) { - uplinks = uplinks.split(/[\s,]+/); - } - - let importEntries; - - async.waterfall( - [ - function readImportFile(callback) { - fs.readFile(importPath, 'utf8', (err, importData) => { - if(err) { - return callback(err); - } - - importEntries = getImportEntries(importType, importData); - if(0 === importEntries.length) { - return callback(Errors.Invalid('Invalid or empty import file')); - } - - // We should have enough to validate uplinks - if('bbs' === importType) { - for(let i = 0; i < importEntries.length; ++i) { - if(!validateUplinks(importEntries[i].uplinks)) { - return callback(Errors.Invalid('Invalid uplink(s)')); - } - } - } else { - if(!validateUplinks(uplinks)) { - return callback(Errors.Invalid('Invalid uplink(s)')); - } - } - - return callback(null); - }); - }, - function init(callback) { - return initConfigAndDatabases(callback); - }, - function validateAndCollectInput(callback) { - const msgArea = require('../../core/message_area.js'); - const sysConfig = require('../../core/config.js').get(); - - let msgConfs = msgArea.getSortedAvailMessageConferences(null, { noClient : true } ); - if(!msgConfs) { - return callback(Errors.DoesNotExist('No conferences exist in your configuration')); - } - - msgConfs = msgConfs.map(mc => { - return { - name : mc.conf.name, - value : mc.confTag, - }; - }); - - if(confTag && !msgConfs.find(mc => { - return confTag === mc.value; - })) - { - return callback(Errors.DoesNotExist(`Conference "${confTag}" does not exist`)); - } - - let existingNetworkNames = []; - if(_.has(sysConfig, 'messageNetworks.ftn.networks')) { - existingNetworkNames = Object.keys(sysConfig.messageNetworks.ftn.networks); - } - - if(0 === existingNetworkNames.length) { - return callback(Errors.DoesNotExist('No FTN style networks exist in your configuration')); - } - - if(networkName && !existingNetworkNames.find(net => networkName === net)) { - return callback(Errors.DoesNotExist(`FTN style Network "${networkName}" does not exist`)); - } - - getAnswers([ - { - name : 'confTag', - message : 'Message conference:', - type : 'list', - choices : msgConfs, - pageSize : 10, - when : !confTag, - }, - { - name : 'networkName', - message : 'Network name:', - type : 'list', - choices : existingNetworkNames, - when : !networkName, - }, - { - name : 'uplinks', - message : 'Uplink(s) (comma separated):', - type : 'input', - validate : (input) => { - const inputUplinks = input.split(/[\s,]+/); - return validateUplinks(inputUplinks) ? true : 'Invalid uplink(s)'; - }, - when : !uplinks && 'bbs' !== importType, - } - ], - answers => { - confTag = confTag || answers.confTag; - networkName = networkName || answers.networkName; - uplinks = uplinks || answers.uplinks; - - importEntries.forEach(ie => { - ie.areaTag = ie.ftnTag.toLowerCase(); - }); - - return callback(null); - }); - }, - function confirmWithUser(callback) { - const sysConfig = require('../../core/config.js').get(); - - console.info(`Importing the following for "${confTag}" - (${sysConfig.messageConferences[confTag].name} - ${sysConfig.messageConferences[confTag].desc})`); - importEntries.forEach(ie => { - console.info(` ${ie.ftnTag} - ${ie.name}`); - }); - - console.info(''); - console.info('Importing will NOT create required FTN network configurations.'); - console.info('If you have not yet done this, you will need to complete additional steps after importing.'); - console.info('See docs/msg_networks.md for details.'); - console.info(''); - - getAnswers([ - { - name : 'proceed', - message : 'Proceed?', - type : 'confirm', - } - ], - answers => { - return callback(answers.proceed ? null : Errors.General('User canceled')); - }); - - }, - function loadConfigHjson(callback) { - const configPath = getConfigPath(); - fs.readFile(configPath, 'utf8', (err, confData) => { - if(err) { - return callback(err); - } - - let config; - try { - config = hjson.parse(confData, { keepWsc : true } ); - } catch(e) { - return callback(e); - } - return callback(null, config); - - }); - }, - function performImport(config, callback) { - const confAreas = { messageConferences : {} }; - confAreas.messageConferences[confTag] = { areas : {} }; - - const msgNetworks = { messageNetworks : { ftn : { areas : {} } } }; - - importEntries.forEach(ie => { - const specificUplinks = ie.uplinks || uplinks; // AREAS.BBS has specific uplinks per area - - confAreas.messageConferences[confTag].areas[ie.areaTag] = { - name : ie.name, - desc : ie.name, - }; - - msgNetworks.messageNetworks.ftn.areas[ie.areaTag] = { - network : networkName, - tag : ie.ftnTag, - uplinks : specificUplinks - }; - }); - - - const newConfig = _.defaultsDeep(config, confAreas, msgNetworks); - const configPath = getConfigPath(); - - if(!writeConfig(newConfig, configPath)) { - return callback(Errors.UnexpectedState('Failed writing configuration')); - } - - return callback(null); - } - ], - err => { - if(err) { - console.error(err.reason ? err.reason : err.message); - } else { - const addFieldUpd = 'bbs' === importType ? '"name" and "desc"' : '"desc"'; - console.info('Configuration generated.'); - console.info(`You may wish to validate changes made to ${getConfigPath()}`); - console.info(`as well as update ${addFieldUpd} fields, sorting, etc.`); - console.info(''); - } - } - ); - -} - -function getImportEntries(importType, importData) { - let importEntries = []; - - if('na' === importType) { - // - // parse out - // TAG DESC - // - const re = /^([^\s]+)\s+([^\r\n]+)/gm; - let m; - - while( (m = re.exec(importData) )) { - importEntries.push({ - ftnTag : m[1], - name : m[2], - }); - } - } else if ('bbs' === importType) { - // - // Various formats for AREAS.BBS seem to exist. We want to support as much as possible. - // - // SBBS http://www.synchro.net/docs/sbbsecho.html#AREAS.BBS - // CODE TAG UPLINKS - // - // VADV https://www.vadvbbs.com/products/vadv/support/docs/docs_vfido.php#AREAS.BBS - // TAG UPLINKS - // - // Misc - // PATH|OTHER TAG UPLINKS - // - // Assume the second item is TAG and 1:n UPLINKS (space and/or comma sep) after (at the end) - // - const re = /^[^\s]+\s+([^\s]+)\s+([^\n]+)$/gm; - let m; - while ( (m = re.exec(importData) )) { - const tag = m[1]; - - importEntries.push({ - ftnTag : tag, - name : `Area: ${tag}`, - uplinks : m[2].split(/[\s,]+/), - }); - } - } - - return importEntries; -} - function catCurrentConfig() { try { const config = hjson.rt.parse(fs.readFileSync(getConfigPath(), 'utf8')); - const hjsonOpts = Object.assign({}, HJSONStringifyComonOpts, { + const hjsonOpts = Object.assign({}, HJSONStringifyCommonOpts, { colors : false === argv.colors ? false : true, keepWsc : false === argv.comments ? false : true, }); @@ -596,9 +282,8 @@ function handleConfigCommand() { const action = argv._[1]; switch(action) { - case 'new' : return buildNewConfig(); - case 'import-areas' : return importAreas(); - case 'cat' : return catCurrentConfig(); + case 'new' : return buildNewConfig(); + case 'cat' : return catCurrentConfig(); default : return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR); } diff --git a/core/oputil/oputil_help.js b/core/oputil/oputil_help.js index 4d7931e0..f6ac84ba 100644 --- a/core/oputil/oputil_help.js +++ b/core/oputil/oputil_help.js @@ -39,15 +39,8 @@ actions: actions: new generate a new/initial configuration - import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH cat cat current configuration to stdout -import-areas args: - --conf CONF_TAG specify conference tag in which to import areas - --network NETWORK specify network name/key to associate FTN areas - --uplinks UL1,UL2,... specify one or more comma separated uplinks - --type TYPE specifies area import type. valid options are "bbs" and "na" - cat args: --no-color disable color --no-comments strip any comments @@ -99,12 +92,19 @@ general information: FILE_ID a file identifier. see file.sqlite3 `, MessageBase : - `usage: oputil.js mb [] +`usage: oputil.js mb [] - actions: +actions: areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s) one or more commands may be supplied. commands that are multi part such as "%COMPRESS ZIP" should be quoted. + import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH + +import-areas args: + --conf CONF_TAG conference tag in which to import areas + --network NETWORK network name/key to associate FTN areas + --uplinks UL1,UL2,... one or more comma separated uplinks + --type TYPE area import type. valid options are "bbs" and "na" ` }; diff --git a/core/oputil/oputil_message_base.js b/core/oputil/oputil_message_base.js index 60a99a2a..d3653caf 100644 --- a/core/oputil/oputil_message_base.js +++ b/core/oputil/oputil_message_base.js @@ -2,16 +2,25 @@ /* eslint-disable no-console */ 'use strict'; -const printUsageAndSetExitCode = require('./oputil_common.js').printUsageAndSetExitCode; -const ExitCodes = require('./oputil_common.js').ExitCodes; -const argv = require('./oputil_common.js').argv; -const initConfigAndDatabases = require('./oputil_common.js').initConfigAndDatabases; +const { + printUsageAndSetExitCode, + getConfigPath, + ExitCodes, + argv, + initConfigAndDatabases, + getAnswers, + writeConfig, +} = require('./oputil_common.js'); const getHelpFor = require('./oputil_help.js').getHelpFor; const Address = require('../ftn_address.js'); const Errors = require('../enig_error.js').Errors; // deps const async = require('async'); +const paths = require('path'); +const fs = require('fs'); +const hjson = require('hjson'); +const _ = require('lodash'); exports.handleMessageBaseCommand = handleMessageBaseCommand; @@ -121,6 +130,310 @@ function areaFix() { ); } +function validateUplinks(uplinks) { + const ftnAddress = require('../../core/ftn_address.js'); + const valid = uplinks.every(ul => { + const addr = ftnAddress.fromString(ul); + return addr; + }); + return valid; +} + +function getMsgAreaImportType(path) { + if(argv.type) { + return argv.type.toLowerCase(); + } + + return paths.extname(path).substr(1).toLowerCase(); // bbs|na|... +} + +function importAreas() { + const importPath = argv._[argv._.length - 1]; + if(argv._.length < 3 || !importPath || 0 === importPath.length) { + return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR); + } + + const importType = getMsgAreaImportType(importPath); + if('na' !== importType && 'bbs' !== importType) { + return console.error(`"${importType}" is not a recognized import file type`); + } + + // optional data - we'll prompt if for anything not found + let confTag = argv.conf; + let networkName = argv.network; + let uplinks = argv.uplinks; + if(uplinks) { + uplinks = uplinks.split(/[\s,]+/); + } + + let importEntries; + + async.waterfall( + [ + function readImportFile(callback) { + fs.readFile(importPath, 'utf8', (err, importData) => { + if(err) { + return callback(err); + } + + importEntries = getImportEntries(importType, importData); + if(0 === importEntries.length) { + return callback(Errors.Invalid('Invalid or empty import file')); + } + + // We should have enough to validate uplinks + if('bbs' === importType) { + for(let i = 0; i < importEntries.length; ++i) { + if(!validateUplinks(importEntries[i].uplinks)) { + return callback(Errors.Invalid('Invalid uplink(s)')); + } + } + } else { + if(!validateUplinks(uplinks || [])) { + return callback(Errors.Invalid('Invalid uplink(s)')); + } + } + + return callback(null); + }); + }, + function init(callback) { + return initConfigAndDatabases(callback); + }, + function validateAndCollectInput(callback) { + const msgArea = require('../../core/message_area.js'); + const sysConfig = require('../../core/config.js').get(); + + let msgConfs = msgArea.getSortedAvailMessageConferences(null, { noClient : true } ); + if(!msgConfs) { + return callback(Errors.DoesNotExist('No conferences exist in your configuration')); + } + + msgConfs = msgConfs.map(mc => { + return { + name : mc.conf.name, + value : mc.confTag, + }; + }); + + if(confTag && !msgConfs.find(mc => { + return confTag === mc.value; + })) + { + return callback(Errors.DoesNotExist(`Conference "${confTag}" does not exist`)); + } + + const existingNetworkNames = Object.keys(_.get(sysConfig, 'messageNetworks.ftn.networks', {})); + + if(networkName && !existingNetworkNames.find(net => networkName === net)) { + return callback(Errors.DoesNotExist(`FTN style Network "${networkName}" does not exist`)); + } + + // can't use --uplinks without a network + if(!networkName && 0 === existingNetworkNames.length && uplinks) { + return callback(Errors.Invalid('Cannot use --uplinks without an FTN network to import to')); + } + + getAnswers([ + { + name : 'confTag', + message : 'Message conference:', + type : 'list', + choices : msgConfs, + pageSize : 10, + when : !confTag, + }, + { + name : 'networkName', + message : 'FTN network name:', + type : 'list', + choices : [ '-None-' ].concat(existingNetworkNames), + pageSize : 10, + when : !networkName && existingNetworkNames.length > 0, + filter : (choice) => { + return '-None-' === choice ? undefined : choice; + } + }, + ], + answers => { + confTag = confTag || answers.confTag; + networkName = networkName || answers.networkName; + uplinks = uplinks || answers.uplinks; + + importEntries.forEach(ie => { + ie.areaTag = ie.ftnTag.toLowerCase(); + }); + + return callback(null); + }); + }, + function collectUplinks(callback) { + if(!networkName || uplinks || 'bbs' === importType) { + return callback(null); + } + + getAnswers([ + { + name : 'uplinks', + message : 'Uplink(s) (comma separated):', + type : 'input', + validate : (input) => { + const inputUplinks = input.split(/[\s,]+/); + return validateUplinks(inputUplinks) ? true : 'Invalid uplink(s)'; + }, + } + ], + answers => { + uplinks = answers.uplinks; + return callback(null); + }); + }, + function confirmWithUser(callback) { + const sysConfig = require('../../core/config.js').get(); + + console.info(`Importing the following for "${confTag}"`); + console.info(`(${sysConfig.messageConferences[confTag].name} - ${sysConfig.messageConferences[confTag].desc})`); + console.info(''); + importEntries.forEach(ie => { + console.info(` ${ie.ftnTag} - ${ie.name}`); + }); + + if(networkName) { + console.info(''); + console.info(`For FTN network: ${networkName}`); + console.info(`Uplinks: ${uplinks}`); + console.info(''); + console.info('Importing will NOT create required FTN network configurations.'); + console.info('If you have not yet done this, you will need to complete additional steps after importing.'); + console.info('See Message Networks docs for details.'); + console.info(''); + } + + getAnswers([ + { + name : 'proceed', + message : 'Proceed?', + type : 'confirm', + } + ], + answers => { + return callback(answers.proceed ? null : Errors.General('User canceled')); + }); + + }, + function loadConfigHjson(callback) { + const configPath = getConfigPath(); + fs.readFile(configPath, 'utf8', (err, confData) => { + if(err) { + return callback(err); + } + + let config; + try { + config = hjson.parse(confData, { keepWsc : true } ); + } catch(e) { + return callback(e); + } + return callback(null, config); + + }); + }, + function performImport(config, callback) { + const confAreas = { messageConferences : {} }; + confAreas.messageConferences[confTag] = { areas : {} }; + + const msgNetworks = { messageNetworks : { ftn : { areas : {} } } }; + + importEntries.forEach(ie => { + const specificUplinks = ie.uplinks || uplinks; // AREAS.BBS has specific uplinks per area + + confAreas.messageConferences[confTag].areas[ie.areaTag] = { + name : ie.name, + desc : ie.name, + }; + + if(networkName) { + msgNetworks.messageNetworks.ftn.areas[ie.areaTag] = { + network : networkName, + tag : ie.ftnTag, + uplinks : specificUplinks + }; + } + }); + + + const newConfig = _.defaultsDeep(config, confAreas, msgNetworks); + const configPath = getConfigPath(); + + if(!writeConfig(newConfig, configPath)) { + return callback(Errors.UnexpectedState('Failed writing configuration')); + } + + return callback(null); + } + ], + err => { + if(err) { + console.error(err.reason ? err.reason : err.message); + } else { + const addFieldUpd = 'bbs' === importType ? '"name" and "desc"' : '"desc"'; + console.info('Configuration generated.'); + console.info(`You may wish to validate changes made to ${getConfigPath()}`); + console.info(`as well as update ${addFieldUpd} fields, sorting, etc.`); + console.info(''); + } + } + ); +} + +function getImportEntries(importType, importData) { + let importEntries = []; + + if('na' === importType) { + // + // parse out + // TAG DESC + // + const re = /^([^\s]+)\s+([^\r\n]+)/gm; + let m; + + while( (m = re.exec(importData) )) { + importEntries.push({ + ftnTag : m[1].trim(), + name : m[2].trim(), + }); + } + } else if ('bbs' === importType) { + // + // Various formats for AREAS.BBS seem to exist. We want to support as much as possible. + // + // SBBS http://www.synchro.net/docs/sbbsecho.html#AREAS.BBS + // CODE TAG UPLINKS + // + // VADV https://www.vadvbbs.com/products/vadv/support/docs/docs_vfido.php#AREAS.BBS + // TAG UPLINKS + // + // Misc + // PATH|OTHER TAG UPLINKS + // + // Assume the second item is TAG and 1:n UPLINKS (space and/or comma sep) after (at the end) + // + const re = /^[^\s]+\s+([^\s]+)\s+([^\n]+)$/gm; + let m; + while ( (m = re.exec(importData) )) { + const tag = m[1].trim(); + + importEntries.push({ + ftnTag : tag, + name : `Area: ${tag}`, + uplinks : m[2].trim().split(/[\s,]+/), + }); + } + } + + return importEntries; +} + function handleMessageBaseCommand() { function errUsage() { @@ -137,6 +450,7 @@ function handleMessageBaseCommand() { const action = argv._[1]; return({ - areafix : areaFix, + areafix : areaFix, + 'import-areas' : importAreas, }[action] || errUsage)(); } \ No newline at end of file diff --git a/docs/admin/oputil.md b/docs/admin/oputil.md index 9db1439c..1e081afb 100644 --- a/docs/admin/oputil.md +++ b/docs/admin/oputil.md @@ -70,23 +70,18 @@ The `config` command allows sysops to perform various system configuration and m usage: optutil.js config [] actions: - new generate a new/initial configuration - import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH + new generate a new/initial configuration + cat cat current configuration to stdout -import-areas args: - --conf CONF_TAG specify conference tag in which to import areas - --network NETWORK specify network name/key to associate FTN areas - --uplinks UL1,UL2,... specify one or more comma separated uplinks - --type TYPE specifies area import type. valid options are "bbs" and "na" +cat args: + --no-color disable color + --no-comments strip any comments ``` - | Action | Description | Examples | |-----------|-------------------|---------------------------------------| | `new` | Generates a new/initial configuration | `./oputil.js config new` (follow the prompts) | -| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file | `./oputil.js config import-areas /some/path/l33tnet.na` | - -When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args". +| `cat` | Pretty prints current `config.hjson` configuration to stdout. | `./oputil.js config cat` | ## File Base Management The `fb` command provides a powerful file base management interface. @@ -189,3 +184,28 @@ file_crc32: fc6655d file_md5: 3455f74bbbf9539e69bd38f45e039a4e file_sha1: 558fab3b49a8ac302486e023a3c2a86bd4e4b948 ``` + +## Message Base Management +The `mb` command provides various Message Base related tools: + +``` +usage: oputil.js mb [] + +actions: + areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s) + one or more commands may be supplied. commands that are multi + part such as "%COMPRESS ZIP" should be quoted. + import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH + +import-areas args: + --conf CONF_TAG conference tag in which to import areas + --network NETWORK network name/key to associate FTN areas + --uplinks UL1,UL2,... one or more comma separated uplinks + --type TYPE area import type. valid options are "bbs" and "na" +``` + +| Action | Description | Examples | +|-----------|-------------------|---------------------------------------| +| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js config import-areas /some/path/l33tnet.na` | + +When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args".