add readme, add more comments to main.go
This commit is contained in:
parent
db28cfbcdb
commit
b2cfe0f1ab
6
README.md
Normal file
6
README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# go-bbs
|
||||||
|
## a basic multiuser bulletin board system written in Go
|
||||||
|
|
||||||
|
This is a simple framework for developing and deploying a console-based bulletin board system.
|
||||||
|
|
||||||
|
It is currently under active development but not ready for use quite yet.
|
56
main.go
56
main.go
@ -1,3 +1,5 @@
|
|||||||
|
// go-bbs provides a basic framework for a multiuser bulletin board server application
|
||||||
|
// it is indended to be customized by the operator of the bulletin board
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -26,6 +28,11 @@ import (
|
|||||||
type InChanKey string
|
type InChanKey string
|
||||||
type OutChanKey string
|
type OutChanKey string
|
||||||
|
|
||||||
|
// ioHandler is the gaio.Watcher's main loop
|
||||||
|
// it wait for I/O calls to the watcher's Read and Write methods
|
||||||
|
// and tracks the state of the call, dispatching the appropriate gaio.OpResult
|
||||||
|
// to the proper channel based on whether it was a read (input from the remote client)
|
||||||
|
// or a write (output to the remote client)
|
||||||
func ioHandler(w *gaio.Watcher, WatcherControl *chan string) {
|
func ioHandler(w *gaio.Watcher, WatcherControl *chan string) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -71,6 +78,9 @@ func ioHandler(w *gaio.Watcher, WatcherControl *chan string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// main is the main loop of the server
|
||||||
|
// it sets up both the network listener as well as the gaio.Watcher that will handle
|
||||||
|
// I/O for each incoming network connection
|
||||||
func main() {
|
func main() {
|
||||||
port := flag.Int("port", 3333, "Port to accept connections on.")
|
port := flag.Int("port", 3333, "Port to accept connections on.")
|
||||||
host := flag.String("host", "127.0.0.1", "Host or IP to bind to")
|
host := flag.String("host", "127.0.0.1", "Host or IP to bind to")
|
||||||
@ -131,6 +141,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// menuHandler is the main loop for each individual remote connection
|
||||||
|
// it receives input over the connection's InChan channel (part of its context)
|
||||||
|
// as a gaio.OpResult
|
||||||
func menuHandler(ctx context.Context, conn net.Conn, w *gaio.Watcher) {
|
func menuHandler(ctx context.Context, conn net.Conn, w *gaio.Watcher) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
@ -319,10 +332,16 @@ func menuHandler(ctx context.Context, conn net.Conn, w *gaio.Watcher) {
|
|||||||
log.Println("terminating menucontrol for client")
|
log.Println("terminating menucontrol for client")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// screenHandler handles non-interactive commands
|
||||||
|
// it takes a screen name and an optional struct of data to be parsed into the screen's template
|
||||||
|
// it returns the populated screen as a byte array and an error if there's a problem loading the template
|
||||||
|
// screen templates are simple text files with optional go template directives. if the text file includes
|
||||||
|
// ANSI control sequences they will be passed on to the remote client as well.
|
||||||
|
// TODO: add caching of static screens, move ANSI handling out to optional template directives as well
|
||||||
func screenHandler(screenName string, tplData interface{}) (screen []byte, err error) {
|
func screenHandler(screenName string, tplData interface{}) (screen []byte, err error) {
|
||||||
var screenBuf *bytes.Buffer
|
var screenBuf *bytes.Buffer
|
||||||
if tmpl, err := template.ParseFiles("screens/" + screenName + ".ans"); err != nil {
|
if tmpl, err := template.ParseFiles("screens/" + screenName + ".ans"); err != nil {
|
||||||
log.Printf("err loading welcome template: %v\n", err)
|
log.Printf("err loading welcome template: %v\n", err) // // TODO: better error handling
|
||||||
} else {
|
} else {
|
||||||
screenBuf = new(bytes.Buffer)
|
screenBuf = new(bytes.Buffer)
|
||||||
tmpl.Execute(screenBuf, tplData)
|
tmpl.Execute(screenBuf, tplData)
|
||||||
@ -331,6 +350,11 @@ func screenHandler(screenName string, tplData interface{}) (screen []byte, err e
|
|||||||
return screenBuf.Bytes(), err
|
return screenBuf.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// doorHandler handles interactive commands
|
||||||
|
// it halts the processing of the menuHandler and allows the doorHandler full duplex I/O
|
||||||
|
// between the remote connection and a local program's stdin/stdout
|
||||||
|
// doorHandler returns control to menuHandler once the program completes and exits
|
||||||
|
// TODO: add argument for program, program's prompt, launch text, and exit text
|
||||||
func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.WaitGroup) error {
|
func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.WaitGroup) error {
|
||||||
defer menuwg.Done()
|
defer menuwg.Done()
|
||||||
|
|
||||||
@ -340,21 +364,19 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if inChan, _, ok = openconn.FromContext(ctx); !ok {
|
if inChan, _, ok = openconn.FromContext(ctx); !ok {
|
||||||
log.Println("Could not get inChan/outChan from context!", ok, ctx)
|
log.Println("Could not get inChan/outChan from context!", ok, ctx) // TODO: better error handling
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("/usr/games/adventure")
|
cmd := exec.Command("/usr/games/adventure") // TODO: move command path and args to an argument to doorHandler
|
||||||
|
|
||||||
ptmx, err := pty.Start(cmd)
|
ptmx, err := pty.Start(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Make sure to close the pty at the end.
|
// Make sure to close the pty at the end.
|
||||||
defer func() { _ = ptmx.Close() }() // Best effort.
|
defer func() { _ = ptmx.Close() }() // Best effort. TODO: better error handling, better zombie handling
|
||||||
|
|
||||||
// Can I do this?
|
|
||||||
if term.IsTerminal(int(ptmx.Fd())) {
|
if term.IsTerminal(int(ptmx.Fd())) {
|
||||||
log.Println("Making cmd terminal raw")
|
|
||||||
oldState, _ := term.MakeRaw(int(ptmx.Fd()))
|
oldState, _ := term.MakeRaw(int(ptmx.Fd()))
|
||||||
defer term.Restore(int(ptmx.Fd()), oldState)
|
defer term.Restore(int(ptmx.Fd()), oldState)
|
||||||
}
|
}
|
||||||
@ -370,13 +392,12 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
|
|||||||
select {
|
select {
|
||||||
case result, ok := <-inChan:
|
case result, ok := <-inChan:
|
||||||
var l int
|
var l int
|
||||||
log.Println("i")
|
|
||||||
if ok {
|
if ok {
|
||||||
if result.Operation == gaio.OpRead && result.Size > 0 {
|
if result.Operation == gaio.OpRead && result.Size > 0 {
|
||||||
l, err = ptmx.Write(result.Buffer[:result.Size])
|
l, err = ptmx.Write(result.Buffer[:result.Size])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Println("EOF on cmd.write!!!")
|
log.Println("EOF on cmd.write!!!") // TODO: better error handling
|
||||||
break IOLoop
|
break IOLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,20 +408,19 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
|
|||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Println("yikes, problem getting a result from inChan in doorHandler!")
|
log.Println("yikes, problem getting a result from inChan in doorHandler!") // TODO: better error handling
|
||||||
}
|
}
|
||||||
case <-Terminator:
|
case <-Terminator:
|
||||||
log.Println("Terminator'd!!!")
|
log.Println("Terminator'd!!!")
|
||||||
break IOLoop
|
break IOLoop
|
||||||
default:
|
default:
|
||||||
if !waitingForInput {
|
if !waitingForInput {
|
||||||
log.Println("o")
|
|
||||||
ptmx.SetReadDeadline(time.Now().Add(1 * time.Second))
|
ptmx.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||||
var l int
|
var l int
|
||||||
outBuf := make([]byte, 4096)
|
outBuf := make([]byte, 4096)
|
||||||
if l, err = ptmx.Read(outBuf); err != nil {
|
if l, err = ptmx.Read(outBuf); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Println("EOF on cmd stdout - outta here")
|
// EOF isn't actually an error in this case, it just means we're done
|
||||||
break IOLoop
|
break IOLoop
|
||||||
} else if errors.Is(err, os.ErrDeadlineExceeded) {
|
} else if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||||
// output deadline exceeded - get some input instead
|
// output deadline exceeded - get some input instead
|
||||||
@ -410,20 +430,19 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
|
|||||||
// program exited - get outta here
|
// program exited - get outta here
|
||||||
break IOLoop
|
break IOLoop
|
||||||
} else {
|
} else {
|
||||||
log.Printf("err reading from cmd term: %v\n", err)
|
log.Printf("err reading from cmd term: %v\n", err) // TODO: better error handling
|
||||||
break IOLoop
|
break IOLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if l > 0 {
|
if l > 0 {
|
||||||
w.Write(ctx, c, outBuf[:l])
|
w.Write(ctx, c, outBuf[:l])
|
||||||
log.Printf("wrote %d bytes to watcher: %v\n", l, string(outBuf[:l]))
|
log.Printf("wrote %d bytes to watcher: %v\n", l, string(outBuf[:l]))
|
||||||
if strings.Contains(string(outBuf[:l]), ">") {
|
if strings.Contains(string(outBuf[:l]), ">") { // TODO: abstract out prompt string as argument to doorHandler
|
||||||
// a prompt appeared!
|
// a prompt appeared!
|
||||||
waitingForInput = true
|
waitingForInput = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// zero output? let's get some input?
|
// zero output? let's get some input opportunistically
|
||||||
// waitingForInput = true
|
|
||||||
w.Read(ctx, c, nil)
|
w.Read(ctx, c, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -435,7 +454,7 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
|
|||||||
log.Println("waiting for cmd")
|
log.Println("waiting for cmd")
|
||||||
exitErr := cmd.Wait()
|
exitErr := cmd.Wait()
|
||||||
if exitErr != nil {
|
if exitErr != nil {
|
||||||
log.Printf("cmd exited with err: %v\n", exitErr)
|
log.Printf("cmd exited with err: %v\n", exitErr) // TODO: better error handling
|
||||||
}
|
}
|
||||||
log.Println("leaving door handler")
|
log.Println("leaving door handler")
|
||||||
return nil
|
return nil
|
||||||
@ -446,11 +465,12 @@ func getUptime(wg *sync.WaitGroup) string {
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
out, err := exec.Command("uptime").Output()
|
out, err := exec.Command("uptime").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return string(err.Error()) // might as well pass on the love to my future co-workers for debugging assistance
|
return string(err.Error()) // TODO: better error handling
|
||||||
}
|
}
|
||||||
return string(out)
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns output of a bash script that collects current wireguard peering connections and their traffic totals
|
||||||
func getTubePeers(wg *sync.WaitGroup) string {
|
func getTubePeers(wg *sync.WaitGroup) string {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
out, err := exec.Command("/usr/local/bin/getTubesPeers.bash").Output()
|
out, err := exec.Command("/usr/local/bin/getTubesPeers.bash").Output()
|
||||||
@ -477,7 +497,7 @@ func getTubePeers(wg *sync.WaitGroup) string {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("err loading getTubePeers: %v\n", err)
|
log.Printf("err loading getTubePeers: %v\n", err)
|
||||||
return string(err.Error()) // again, might as well pass on the love to my future co-workers for debugging assistance
|
return string(err.Error()) // TODO: better error handling
|
||||||
}
|
}
|
||||||
return outStr
|
return outStr
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user