Merge branch '0.0.9-alpha' of github.com:NuSkooler/enigma-bbs into user-interruptions

This commit is contained in:
Bryan Ashby 2018-11-24 20:26:19 -07:00
commit c0de3d7048
8 changed files with 437 additions and 467 deletions

View File

@ -216,35 +216,36 @@ 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
//
const User = require('./user.js');
const propLoadOpts = {
names : [
UserProps.RealName, UserProps.Sex, UserProps.EmailAddress,
UserProps.Location, UserProps.Affiliations,
],
};
async.waterfall(
[
function getOpUserName(next) {
return User.getUserName(1, next);
},
function getOpProps(opUserName, next) {
const propLoadOpts = {
names : [
UserProps.RealName, UserProps.Sex, UserProps.EmailAddress,
UserProps.Location, UserProps.Affiliations,
],
};
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => {
return next(err, opUserName, opProps, propLoadOpts);
return next(err, opUserName, opProps);
});
}
},
],
(err, opUserName, opProps, propLoadOpts) => {
(err, opUserName, opProps) => {
const StatLog = require('./stat_log.js');
if(err) {
propLoadOpts.concat('username').forEach(v => {
propLoadOpts.names.concat('username').forEach(v => {
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
});
} else {

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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 <action> [<args>]
`usage: oputil.js mb <action> [<args>]
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"
`
};

View File

@ -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)();
}

View File

@ -70,23 +70,18 @@ The `config` command allows sysops to perform various system configuration and m
usage: optutil.js config <action> [<args>]
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 <action> [<args>]
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".

View File

@ -44,90 +44,5 @@ Below is a list of various configuration sections. There are many more, but this
* [File Base](/docs/filebase/index.md)
* [File Transfer Protocols](file-transfer-protocols.md): Oldschool file transfer protocols such as X/Y/Z-Modem!
* [Message Areas](/docs/messageareas/configuring-a-message-area.md), [Networks](/docs/messageareas/message-networks.md), [NetMail](/docs/messageareas/netmail.md), etc.
* ...and a **lot** more! Explore the docs! If you can't find something, please contact us!
### A Sample Configuration
Below is a **sample** `config.hjson` illustrating various (but certainly not all!) elements that can be configured / tweaked.
**This is for illustration purposes! Do not cut & paste this configuration!**
```hjson
{
general: {
boardName: A Sample BBS
menuFile: "your_bbs.hjson" // copy of menu.hjson file (and adapt to your needs)
}
theme: {
default: "super-fancy-theme" // default-assigned theme (for new users)
}
messageConferences: {
local_general: {
name: Local
desc: Local Discussions
default: true
areas: {
local_enigma_dev: {
name: ENiGMA 1/2 Development
desc: Discussion related to development and features of ENiGMA 1/2!
default: true
}
}
}
agoranet: {
name: Agoranet
desc: This network is for blatant exploitation of the greatest BBS scene art group ever.. ACiD.
areas: {
agoranet_bbs: {
name: BBS Discussion
desc: Discussion related to BBSs
}
}
}
}
messageNetworks: {
ftn: {
areas: {
agoranet_bbs: { /* hey kids, this matches above! */
// oh oh oh, and this one pairs up with a network below
network: agoranet
tag: AGN_BBS
uplinks: "46:1/100"
}
}
networks: {
agoranet: {
localAddress: "46:3/102"
}
}
}
}
scannerTossers: {
ftn_bso: {
schedule: {
import: every 1 hours or @watch:/home/enigma/bink/watchfile.txt
export: every 1 hours or @immediate
}
defaultZone: 46
defaultNetwork: agoranet
nodes: {
"46:*": {
archiveType: ZIP
encoding: utf8
}
}
}
}
}
```

View File

@ -2,29 +2,28 @@
layout: page
title: Configuring a Message Area
---
## Message Conferences
**Message Conferences** and **Areas** allow for grouping of message base topics.
## Message Conferences
Message Conferences are the top level container for 1:n Message Areas via the `messageConferences` section
in `config.hjson`. Common message conferences may include a local conference and one or more conferences
each dedicated to a particular message network such as FsxNet, AgoraNet, etc.
## Conferences
Message Conferences are the top level container for *1:n* Message *Areas* via the `messageConferences` block in `config.hjson`. A common setup may include a local conference and one or more conferences each dedicated to a particular message network such as fsxNet, ArakNet, etc.
Each conference is represented by a entry under `messageConferences`. **The areas key is the conferences tag**.
Each conference is represented by a entry under `messageConferences`. Each entries top level key is it's *conference tag*.
| Config Item | Required | Description |
|-------------|----------|---------------------------------------------------------------------------------|
| `name` | :+1: | Friendly conference name |
| `desc` | :+1: | Friendly conference description |
| `sort` | :-1: | If supplied, provides a key used for sorting |
| `name` | :+1: | Friendly conference name |
| `desc` | :+1: | Friendly conference description. |
| `sort` | :-1: | Set to a number to override the default alpha-numeric sort order based on the `name` field. |
| `default` | :-1: | Specify `true` to make this the default conference (e.g. assigned to new users) |
| `areas` | :+1: | Container of 1:n areas described below |
| `areas` | :+1: | Container of 1:n areas described below |
### Example
```hjson
{
messageConferences: {
local: {
local: { // conference tag
name: Local
desc: Local discussion
sort: 1
@ -35,16 +34,14 @@ Each conference is represented by a entry under `messageConferences`. **The area
```
## Message Areas
Message Areas are topic specific containers for messages that live within a particular conference. #
**The area's key is its area tag**. For example, "General Discussion" may live under a Local conference
while an AgoraNet conference may contain "BBS Discussion".
Message Areas are topic specific containers for messages that live within a particular conference. The top level key for an area sets it's *area tag*. For example, "General Discussion" may live under a Local conference while an fsxNet conference may contain "BBS Discussion".
| Config Item | Required | Description |
|-------------|----------|---------------------------------------------------------------------------------|
| `name` | :+1: | Friendly area name |
| `desc` | :+1: | Friendly area discription |
| `sort` | :-1: | If supplied, provides a key used for sorting |
| `default` | :-1: | Specify `true` to make this the default area (e.g. assigned to new users) |
| `name` | :+1: | Friendly area name. |
| `desc` | :+1: | Friendly area description. |
| `sort` | :-1: | Set to a number to override the default alpha-numeric sort order based on the `name` field. |
| `default` | :-1: | Specify `true` to make this the default area (e.g. assigned to new users) |
### Example
@ -62,4 +59,7 @@ messageConferences: {
}
}
}
```
```
## Importing
FidoNet style `.na` files as well as legacy `AREAS.BBS` files in common formats can be imported using `oputil.js mb import-areas`. See [The oputil CLI](/docs/admin/oputil.md) for more information and usage.