Skip to content

Commit

Permalink
Continued work on web host feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp Heckel committed Sep 26, 2021
1 parent 3b7b5eb commit ee1ee66
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 5 deletions.
51 changes: 51 additions & 0 deletions bot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"heckel.io/replbot/util"
"log"
"net"
"net/http"
"net/http/httputil"
"os"
"strings"
"sync"
Expand Down Expand Up @@ -62,6 +64,7 @@ type Bot struct {
conn conn
sessions map[string]*session
shareUser map[string]*session
webID map[string]*session
cancelFn context.CancelFunc
mu sync.RWMutex
}
Expand Down Expand Up @@ -110,6 +113,11 @@ func (b *Bot) Run() error {
return b.runShareServer(ctx)
})
}
if b.config.WebHost != "" {
g.Go(func() error {
return b.runWebServer(ctx)
})
}
return g.Wait()
}

Expand Down Expand Up @@ -368,6 +376,49 @@ func (b *Bot) handleHelp(channel, thread string, err error) error {
return b.conn.Send(target, message)
}

func (b *Bot) runWebServer(ctx context.Context) error {
_, port, err := net.SplitHostPort(b.config.WebHost)
if err != nil {
return err
}
if port == "" {
port = "80"
}
errChan := make(chan error)
go func() {
http.HandleFunc("/", b.webHandler)
errChan <- http.ListenAndServe(":"+port, nil)
}()
select {
case err := <-errChan:
return err
case <-ctx.Done():
return nil
}
}

func (b *Bot) webHandler(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")
if len(parts) < 3 { // must be /prefix/, not just /prefix
w.WriteHeader(http.StatusBadRequest)
return
}
prefix := parts[1]
if prefix != "a" {
w.WriteHeader(http.StatusBadRequest)
return
}
proxy := &httputil.ReverseProxy{Director: func(r *http.Request) {
r.URL.Scheme = "http"
r.URL.Host = "localhost:10001"
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/a")
r.URL.RawPath = strings.TrimPrefix(r.URL.Path, "/a")
r.Header.Set("Origin", fmt.Sprintf("http://%s", b.config.WebHost))
log.Printf("webProxyRequest: host=%s path=%s", r.Host, r.URL.Path)
}}
proxy.ServeHTTP(w, r)
}

func (b *Bot) runShareServer(ctx context.Context) error {
if err := os.WriteFile(shareServerScriptFile, []byte(shareServerScriptSource), 0700); err != nil {
return err
Expand Down
26 changes: 21 additions & 5 deletions bot/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const (
"Other commands:\n" +
" `!! ..` - Comment, ignored entirely\n" +
" `!allow ..`, `!deny ..` - Allow/deny users\n" +
" `!web` - Start web terminal\n" +
" `!web` - Start/stop web terminal\n" +
" `!resize ..` - Resize window\n" +
" `!screen`, `!s` - Re-send terminal\n" +
" `!alive` - Reset session timeout\n" +
Expand Down Expand Up @@ -187,7 +187,7 @@ type sessionConfig struct {
size *config.Size
share *shareConfig
record bool
webTerm bool
web bool
}

type shareConfig struct {
Expand Down Expand Up @@ -271,6 +271,9 @@ func (s *session) Run() error {
if err != nil {
return err
}
if err := s.maybeStartWeb(); err != nil {
return err
}
if err := s.maybeSendStartShareMessage(); err != nil {
return err
}
Expand Down Expand Up @@ -519,12 +522,12 @@ func (s *session) sessionStartedMessage() string {
switch s.conf.authMode {
case config.OnlyMe:
message += "\n\n" + onlyMeModeMessage
if s.conf.webTerm {
if s.conf.web {
message += "\n\n" + "You may also view the session here: http://plop.datto.lan/lalala"
}
case config.Everyone:
message += "\n\n" + everyoneModeMessage
if s.conf.webTerm {
if s.conf.web {
message += "\n\n" + "*Danger*: In addition to this chat, you can also control the session from here: http://plop.datto.lan/lalala"
}
}
Expand Down Expand Up @@ -718,6 +721,14 @@ func (s *session) sendExitedMessageWithRecording() error {
return s.conn.UploadFile(s.conf.control, message, recordingFileName, recordingFileType, file)
}

func (s *session) maybeStartWeb() error {
if !s.conf.web {
return nil
}
// FIXME
return s.maybeRestartGotty()
}

func (s *session) maybeSendStartShareMessage() error {
if s.conf.share == nil {
return nil
Expand Down Expand Up @@ -769,7 +780,7 @@ func (s *session) handleEscapeCommand(input string) error {
return s.tmux.Paste(input)
}

func (s *session) handleKeepaliveCommand(input string) error {
func (s *session) handleKeepaliveCommand(_ string) error {
return s.conn.Send(s.conf.control, sessionKeptAliveMessage)
}

Expand Down Expand Up @@ -885,6 +896,11 @@ func (s *session) handleWebCommand(input string) error {
func (s *session) maybeRestartGotty() error {
s.mu.Lock()
defer s.mu.Unlock()
/*host, port, err := net.SplitHostPort(s.conf.global.WebHost)
if err != nil {
return err
}*/

if s.webCmd != nil && s.webCmd.Process != nil {
_ = s.webCmd.Process.Kill()
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func New() *cli.App {
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "upload-recording", Aliases: []string{"z"}, EnvVars: []string{"REPLBOT_UPLOAD_RECORDING"}, Usage: "upload recorded sessions via 'asciinema upload'"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "no-upload-recording", Aliases: []string{"Z"}, EnvVars: []string{"REPLBOT_NO_UPLOAD_RECORDING"}, Usage: "do not upload recorded sessions via 'asciinema upload'"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "cursor", Aliases: []string{"C"}, EnvVars: []string{"REPLBOT_CURSOR"}, Value: "on", Usage: "cursor blink rate (on, off or duration)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-host", Aliases: []string{"W"}, EnvVars: []string{"REPLBOT_WEB_ADDRESS"}, Usage: "hostname:port used to provide the web terminal feature"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "share-host", Aliases: []string{"H"}, EnvVars: []string{"REPLBOT_SHARE_HOST"}, Usage: "SSH hostname:port, used for terminal sharing"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "share-key-file", Aliases: []string{"K"}, EnvVars: []string{"REPLBOT_SHARE_KEY_FILE"}, Value: "/etc/replbot/hostkey", Usage: "SSH host key file, used for terminal sharing"}),
}
Expand Down Expand Up @@ -68,6 +69,7 @@ func execRun(c *cli.Context) error {
defaultWindowMode := config.WindowMode(c.String("default-window-mode"))
defaultAuthMode := config.AuthMode(c.String("default-auth-mode"))
cursor := c.String("cursor")
webHost := c.String("web-host")
shareHost := c.String("share-host")
shareKeyFile := c.String("share-key-file")
debug := c.Bool("debug")
Expand Down Expand Up @@ -128,6 +130,7 @@ func execRun(c *cli.Context) error {
conf.DefaultRecord = defaultRecord
conf.UploadRecording = uploadRecording
conf.Cursor = cursorRate
conf.WebHost = webHost
conf.ShareHost = shareHost
conf.ShareKeyFile = shareKeyFile
conf.Debug = debug
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Config struct {
DefaultAuthMode AuthMode
DefaultSize *Size
Cursor time.Duration
WebHost string
ShareHost string
ShareKeyFile string
DefaultRecord bool
Expand Down
11 changes: 11 additions & 0 deletions config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ bot-token: MUST_BE_SET
#
# upload-recording: false

# Hostname and port of the web server to support the web terminal feature via the !web command.
# The socket is bound to :port, but the hostname is used to provide the full URL.
#
# If no port is provided, port 80 is assumed.
#
# Format: <hostname>[:<port>]
# Default: empty
# Required: No
#
# web-address:

# Timeout after which REPL sessions are terminated if there is no user input.
#
# Format: <number>(hms), must be >1m
Expand Down

0 comments on commit ee1ee66

Please sign in to comment.