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
|
||||
|
||||
import (
|
||||
@ -26,6 +28,11 @@ import (
|
||||
type InChanKey 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) {
|
||||
for {
|
||||
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() {
|
||||
port := flag.Int("port", 3333, "Port to accept connections on.")
|
||||
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) {
|
||||
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")
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var screenBuf *bytes.Buffer
|
||||
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 {
|
||||
screenBuf = new(bytes.Buffer)
|
||||
tmpl.Execute(screenBuf, tplData)
|
||||
@ -331,6 +350,11 @@ func screenHandler(screenName string, tplData interface{}) (screen []byte, err e
|
||||
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 {
|
||||
defer menuwg.Done()
|
||||
|
||||
@ -340,21 +364,19 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
|
||||
var ok bool
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 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())) {
|
||||
log.Println("Making cmd terminal raw")
|
||||
oldState, _ := term.MakeRaw(int(ptmx.Fd()))
|
||||
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 {
|
||||
case result, ok := <-inChan:
|
||||
var l int
|
||||
log.Println("i")
|
||||
if ok {
|
||||
if result.Operation == gaio.OpRead && result.Size > 0 {
|
||||
l, err = ptmx.Write(result.Buffer[:result.Size])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
log.Println("EOF on cmd.write!!!")
|
||||
log.Println("EOF on cmd.write!!!") // TODO: better error handling
|
||||
break IOLoop
|
||||
}
|
||||
}
|
||||
@ -387,20 +408,19 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
|
||||
|
||||
}
|
||||
} 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:
|
||||
log.Println("Terminator'd!!!")
|
||||
break IOLoop
|
||||
default:
|
||||
if !waitingForInput {
|
||||
log.Println("o")
|
||||
ptmx.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
var l int
|
||||
outBuf := make([]byte, 4096)
|
||||
if l, err = ptmx.Read(outBuf); err != nil {
|
||||
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
|
||||
} else if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
// 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
|
||||
break IOLoop
|
||||
} 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
|
||||
}
|
||||
}
|
||||
if l > 0 {
|
||||
w.Write(ctx, c, 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!
|
||||
waitingForInput = true
|
||||
}
|
||||
} else {
|
||||
// zero output? let's get some input?
|
||||
// waitingForInput = true
|
||||
// zero output? let's get some input opportunistically
|
||||
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")
|
||||
exitErr := cmd.Wait()
|
||||
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")
|
||||
return nil
|
||||
@ -446,11 +465,12 @@ func getUptime(wg *sync.WaitGroup) string {
|
||||
defer wg.Done()
|
||||
out, err := exec.Command("uptime").Output()
|
||||
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)
|
||||
}
|
||||
|
||||
// returns output of a bash script that collects current wireguard peering connections and their traffic totals
|
||||
func getTubePeers(wg *sync.WaitGroup) string {
|
||||
defer wg.Done()
|
||||
out, err := exec.Command("/usr/local/bin/getTubesPeers.bash").Output()
|
||||
@ -477,7 +497,7 @@ func getTubePeers(wg *sync.WaitGroup) string {
|
||||
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user