Gopher server revamp!

* gophermap support
* More generic and flexible server
This commit is contained in:
Bryan Ashby 2020-11-27 00:54:56 -07:00
parent 228cd79989
commit f7e4b57763
No known key found for this signature in database
GPG Key ID: B49EB437951D2542
8 changed files with 98 additions and 29 deletions

View File

@ -9,6 +9,7 @@ This document attempts to track **major** changes and additions in ENiGMA½. For
* New `PV` ACS check for arbitrary user properties. See [ACS](./docs/configuration/acs.md) for details.
* The `message` arg used by `msg_list` has been deprecated. Please starting using `messageIndex` for this purpose. Support for `message` will be removed in the future.
* Added ability to export/download messages. This is enabled in the default menu. See `messageAreaViewPost` in [the default message base template](./misc/menu_templates/message_base.in.hjson) and look for the download options (`@method:addToDownloadQueue`, etc.) for details on adding to your system!
* The Gopher server has had a revamp! Standard `gophermap` files are now served along with any other content you configure for your Gopher Hole! A default [gophermap](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) can be found [in the misc directory](./misc/gophermap) that behaves like the previous implementation. See [Gopher docs](./docs/servers/gopher.md) for more information.
## 0.0.11-beta
* Upgraded from `alpha` to `beta` -- The software is far along and mature enough at this point!

View File

@ -263,7 +263,7 @@ module.exports = () => {
port : 8070,
publicHostname : 'another-fine-enigma-bbs.org',
publicPort : 8070, // adjust if behind NAT/etc.
bannerFile : 'gopher_banner.asc',
staticRoot : paths.join(__dirname, './../gopher'),
//
// Set messageConferences{} to maps of confTag -> [ areaTag1, areaTag2, ... ]

View File

@ -27,6 +27,7 @@ const _ = require('lodash');
const fs = require('graceful-fs');
const paths = require('path');
const moment = require('moment');
const async = require('async');
const ModuleInfo = exports.moduleInfo = {
name : 'Gopher',
@ -81,8 +82,8 @@ exports.getModule = class GopherModule extends ServerModule {
this.publicHostname = config.contentServers.gopher.publicHostname;
this.publicPort = config.contentServers.gopher.publicPort;
this.addRoute(/^\/?\r\n$/, this.defaultGenerator);
this.addRoute(/^\/msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/, this.messageAreaGenerator);
this.addRoute(/^\/?msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/, this.messageAreaGenerator);
this.addRoute(/^(\/?[^\t\r\n]*)\r\n$/, this.staticGenerator);
this.server = net.createServer( socket => {
socket.setEncoding('ascii');
@ -161,20 +162,54 @@ exports.getModule = class GopherModule extends ServerModule {
return `${itemType}${text}\t${selector}\t${hostname}\t${port}\r\n`;
}
defaultGenerator(selectorMatch, cb) {
this.log.debug( { selector : selectorMatch[0] }, 'Serving default content');
staticGenerator(selectorMatch, cb) {
this.log.debug( { selector : selectorMatch[1] || '(gophermap)' }, 'Serving static content');
let bannerFile = _.get(Config(), 'contentServers.gopher.bannerFile', 'gopher_banner.asc');
bannerFile = paths.isAbsolute(bannerFile) ? bannerFile : paths.join(__dirname, '../../../misc', bannerFile);
fs.readFile(bannerFile, 'utf8', (err, banner) => {
if(err) {
return cb('You have reached an ENiGMA½ Gopher server!');
const requestedPath = selectorMatch[1];
let path = this.resolveContentPath(requestedPath);
if (!path) {
return cb('Not found');
}
banner = splitTextAtTerms(banner).map(l => this.makeItem(ItemTypes.InfoMessage, l)).join('');
banner += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea');
return cb(banner);
fs.stat(path, (err, stats) => {
if (err) {
return cb('Not found');
}
let isGopherMap = false;
if (stats.isDirectory()) {
path = paths.join(path, 'gophermap');
isGopherMap = true;
}
fs.readFile(path, isGopherMap ? 'utf8' : null, (err, content) => {
if (err) {
let content = 'You have reached an ENiGMA½ Gopher server!\r\n';
content += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea');
return cb(content);
}
if (isGopherMap) {
// Convert any UNIX style LF's to DOS CRLF's
content = content.replace(/\r?\n/g, '\r\n');
// variable support
content = content
.replace(/{publicHostname}/g, this.publicHostname)
.replace(/{publicPort}/g, this.publicPort);
}
return cb(content);
});
});
}
resolveContentPath(requestPath) {
const staticRoot = _.get(Config(), 'contentServers.gopher.staticRoot');
const path = paths.resolve(staticRoot, `.${requestPath}`);
if (path.startsWith(staticRoot)) {
return path;
}
}
notFoundGenerator(selector, cb) {

View File

@ -3,7 +3,7 @@ layout: page
title: Gopher Server
---
## The Gopher Content Server
The Gopher *content server* provides access to publicly exposed message conferences and areas over Gopher (gopher://).
The Gopher *content server* provides access to publicly exposed message conferences and areas over Gopher (gopher://) as well as any other content you wish to serve in your Gopher Hole!
## Configuration
Gopher configuration is found in `contentServers.gopher` in `config.hjson`.
@ -11,14 +11,36 @@ Gopher configuration is found in `contentServers.gopher` in `config.hjson`.
| Item | Required | Description |
|------|----------|-------------|
| `enabled` | :+1: | Set to `true` to enable Gopher |
| `staticRoot` | :+1: | Sets the path serving as the static root path for all Gopher content. Defaults to `enigma-bbs/gopher`.<br>See also **Gophermap's** below |
| `port` | :-1: | Override the default port of `8070` |
| `publicHostName` | :+1: | Set the **public** hostname/domain that Gopher will serve to the outside world. Example: `myfancybbs.com` |
| `publicHostname` | :+1: | Set the **public** hostname/domain that Gopher will serve to the outside world. Example: `myfancybbs.com` |
| `publicPort` | :+1: | Set the **public** port that Gopher will serve to the outside world. |
| `messageConferences` | :+1: | An map of *conference tags* to *area tags* that are publicly exposed via Gopher. See example below. |
| `messageConferences` | :-1: | An map of *conference tags* to *area tags* that are publicly exposed via Gopher. See example below. |
Notes on `publicHostName` and `publicPort`:
Notes on `publicHostname` and `publicPort`:
The Gopher protocol serves content that contains host/domain and port even when referencing it's own documents. Due to this, these members must be set to your publicly addressable Gopher server!
## Gophermap's
[Gophermap's](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) are how to build menus for your Gopher Hole. Each map is a simple text file named `gophermap` (all lowercase, no extension) with DOS style CRLF endings.
Within any directory nested within your `staticRoot` may live a `gophermap`. A template may be found in the `enigma-bbsmisc` directory.
ENiGMA will pre-process `gophermap` files replacing in following variables:
* `{publicHostname}`: The public hostname from your config.
* `{publicPort}`: The public port from your config.
:information_source: See [Wikipedia](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) for more information on the `gophermap` format.
:bulb: Tools such as [gfu](https://rawtext.club/~sloum/gfu.html) may help you with `gophermap`'s
### Example Gophermap
An example `gophermap` living in `enigma-bbs/gopher`:
```
iWelcome to a Gopher server! {publicHostname} {publicPort}
1Public Message Area /msgarea {publicHostname} {publicPort}
.
```
### Example
Let's suppose you are serving Gopher for your BBS at `myfancybbs.com`. Your ENiGMA½ system is listening on the default Gopher `port` of 8070 but you're behind a firewall and want port 70 exposed to the public. Lastly, you want to expose some fsxNet areas:
@ -26,9 +48,10 @@ Let's suppose you are serving Gopher for your BBS at `myfancybbs.com`. Your ENiG
contentServers: {
gopher: {
enabled: true
publicHostName: myfancybbs.com
publicHostname: myfancybbs.com
publicPort: 70
// Expose some public message conferences/areas
messageConferences: {
fsxnet: { // fsxNet's conf tag
// Areas of fsxNet we want to expose:

View File

@ -242,8 +242,8 @@
port: XXXXX
enabled: false
// bannerFile path in misc/ by default. Full paths allowed.
bannerFile: XXXXX
// The root directory to serve gophermaps and other content
staticRoot: XXXXX
//
// The Gopher Content Server can export message base

View File

@ -1,9 +0,0 @@
_____________________ _____ ____________________ __________\_ /
\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp!
// __|___// | \// |// | \// | | \// \ /___ /_____
/____ _____| __________ ___|__| ____| \ / _____ \
---- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/
/__ _\
<*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/
-------------------------------------------------------------------------------

12
misc/gophermap Normal file
View File

@ -0,0 +1,12 @@
i_____________________ _____ ____________________ __________\_ /
i\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp!
i// __|___// | \// |// | \// | | \// \ /___ /_____
i/____ _____| __________ ___|__| ____| \ / _____ \
i---- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/
i /__ _\
i <*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/
i
i-------------------------------------------------------------------------------
i
1Public Message Area /msgarea {publicHostname} {publicPort}
.

View File

@ -146,6 +146,12 @@ install_node_packages() {
fi
}
copy_template_files() {
if [[ ! -f "./gopher/gophermap" ]]; then
cp "./misc/gophermap" "./gopher/gophermap"
fi
}
enigma_footer() {
log "ENiGMA½ installation complete!"
echo -e "\e[1;33m"
@ -189,6 +195,7 @@ install_nvm
configure_nvm
download_enigma_source
install_node_packages
copy_template_files
enigma_footer
} # this ensures the entire script is downloaded before execution