mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
feat: put cmd-hook in container feat: cmd-hook request type, auto restart setup feat: handle ls
180 lines
3.6 KiB
Go
180 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"time"
|
|
|
|
"github.com/creack/pty"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
var (
|
|
addr = flag.String("addr", ":8080", "http service address")
|
|
)
|
|
|
|
const (
|
|
// Time allowed to write a message to the peer.
|
|
writeWait = 10 * time.Second
|
|
|
|
// Maximum message size allowed from peer.
|
|
maxMessageSize = 8192
|
|
|
|
// Time allowed to read the next pong message from the peer.
|
|
pongWait = 60 * time.Second
|
|
|
|
// Send pings to peer with this period. Must be less than pongWait.
|
|
pingPeriod = (pongWait * 9) / 10
|
|
|
|
// Time to wait before force close on connection.
|
|
closeGracePeriod = 10 * time.Second
|
|
)
|
|
|
|
var upgrader = websocket.Upgrader{
|
|
ReadBufferSize: 1024,
|
|
WriteBufferSize: 1024,
|
|
// Allow all origins for now - we'll restrict this in production
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
return true
|
|
},
|
|
}
|
|
|
|
type terminal struct {
|
|
cmd *exec.Cmd
|
|
pty *os.File
|
|
}
|
|
|
|
func newTerminal() (*terminal, error) {
|
|
// Start a new shell
|
|
cmd := exec.Command("bash")
|
|
ptmx, err := pty.Start(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &terminal{
|
|
cmd: cmd,
|
|
pty: ptmx,
|
|
}, nil
|
|
}
|
|
|
|
func (t *terminal) Close() error {
|
|
if t.pty != nil {
|
|
t.pty.Close()
|
|
}
|
|
if t.cmd != nil {
|
|
t.cmd.Process.Kill()
|
|
t.cmd.Wait()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func pumpStdin(ws *websocket.Conn, w io.Writer) {
|
|
defer ws.Close()
|
|
ws.SetReadLimit(maxMessageSize)
|
|
ws.SetReadDeadline(time.Now().Add(pongWait))
|
|
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
|
for {
|
|
_, message, err := ws.ReadMessage()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if _, err := w.Write(message); err != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
|
|
defer func() {
|
|
}()
|
|
buf := make([]byte, 1024)
|
|
for {
|
|
n, err := r.Read(buf)
|
|
if err != nil {
|
|
break
|
|
}
|
|
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
|
if err := ws.WriteMessage(websocket.TextMessage, buf[:n]); err != nil {
|
|
ws.Close()
|
|
break
|
|
}
|
|
}
|
|
close(done)
|
|
|
|
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
|
ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
|
time.Sleep(closeGracePeriod)
|
|
ws.Close()
|
|
}
|
|
|
|
func ping(ws *websocket.Conn, done chan struct{}) {
|
|
ticker := time.NewTicker(pingPeriod)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
|
|
log.Println("ping:", err)
|
|
}
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func internalError(ws *websocket.Conn, msg string, err error) {
|
|
log.Println(msg, err)
|
|
ws.WriteMessage(websocket.TextMessage, []byte("Internal server error."))
|
|
}
|
|
|
|
func serveWs(w http.ResponseWriter, r *http.Request) {
|
|
ws, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Println("upgrade:", err)
|
|
return
|
|
}
|
|
defer ws.Close()
|
|
|
|
// Create new terminal
|
|
term, err := newTerminal()
|
|
if err != nil {
|
|
internalError(ws, "start:", err)
|
|
return
|
|
}
|
|
defer term.Close()
|
|
|
|
stdoutDone := make(chan struct{})
|
|
go pumpStdout(ws, term.pty, stdoutDone)
|
|
go ping(ws, stdoutDone)
|
|
|
|
pumpStdin(ws, term.pty)
|
|
|
|
// Wait for stdout to finish
|
|
<-stdoutDone
|
|
}
|
|
|
|
func serveHome(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" {
|
|
http.Error(w, "Not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
http.ServeFile(w, r, "index.html")
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
log.SetFlags(0)
|
|
http.HandleFunc("/", serveHome)
|
|
http.HandleFunc("/ws", serveWs)
|
|
log.Fatal(http.ListenAndServe(*addr, nil))
|
|
}
|