mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
feat: init cmd-hook and move terminal-service to services directory
feat: put cmd-hook in container feat: cmd-hook request type, auto restart setup feat: handle ls
This commit is contained in:
1 parent
58b6448c3d
commit
0b08a9a5d9
16 files changed
+343
-21
No files matched your search
@@ -0,0 +1,52 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./bin/cmd-hook"
|
||||
cmd = "go build -o ./bin/cmd-hook ."
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
silent = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
@@ -0,0 +1,142 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CommandHandler func(cmd string) (CommandParams, error)
|
||||
|
||||
type CommandParams struct {
|
||||
Discover []FileObject `json:"discover,omitempty"`
|
||||
Add []FileObject `json:"add,omitempty"`
|
||||
Remove []FileObject `json:"remove,omitempty"`
|
||||
Pwd string `json:"pwd,omitempty"`
|
||||
}
|
||||
|
||||
type FileObject struct {
|
||||
Path string `json:"path"`
|
||||
Type FileType `json:"type"`
|
||||
Discovered bool `json:"discovered"`
|
||||
Empty bool `json:"empty,omitempty"`
|
||||
}
|
||||
|
||||
var commandHandlers = map[string]CommandHandler{
|
||||
"ls": handleLs,
|
||||
"cd": handleCd,
|
||||
"touch": handleTouch,
|
||||
"mkdir": handleMkdir,
|
||||
"cp": handleCp,
|
||||
"rm": handleRm,
|
||||
"rmdir": handleRmdir,
|
||||
"mv": handleMv,
|
||||
}
|
||||
|
||||
func handleCommand(cmd string) (CommandParams, error) {
|
||||
parts := strings.Fields(cmd)
|
||||
if len(parts) == 0 {
|
||||
return CommandParams{}, nil
|
||||
}
|
||||
|
||||
handler, exists := commandHandlers[parts[0]]
|
||||
if !exists {
|
||||
return CommandParams{}, nil
|
||||
}
|
||||
|
||||
return handler(cmd)
|
||||
}
|
||||
|
||||
func handleCd(cmd string) (CommandParams, error) {
|
||||
return CommandParams{
|
||||
Pwd: os.Getenv("PWD"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func handleLs(cmd string) (CommandParams, error) {
|
||||
fmt.Println("handleLs", cmd)
|
||||
parts := strings.Fields(cmd)[1:]
|
||||
path := "."
|
||||
if len(parts) > 1 {
|
||||
path = parts[1]
|
||||
}
|
||||
|
||||
files := make([]FileObject, 0)
|
||||
err := discover(&files, path, 0)
|
||||
if err != nil {
|
||||
return CommandParams{}, err
|
||||
}
|
||||
|
||||
return CommandParams{
|
||||
Discover: files,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func discover(files *[]FileObject, path string, level int) error {
|
||||
absPath := resolvePath(path)
|
||||
fileType, err := fileExists(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ls: cannot access '%s': %v", path, err)
|
||||
}
|
||||
|
||||
if fileType == FileTypeFile {
|
||||
*files = append(*files, FileObject{
|
||||
Path: absPath,
|
||||
Type: fileType,
|
||||
Discovered: level <= 1,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading directory: %v", err)
|
||||
}
|
||||
|
||||
*files = append(*files, FileObject{
|
||||
Path: absPath,
|
||||
Type: fileType,
|
||||
Discovered: level <= 1,
|
||||
Empty: len(entries) == 0,
|
||||
})
|
||||
|
||||
if level >= 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
discover(files, filepath.Join(absPath, entry.Name()), level+1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleTouch(cmd string) (CommandParams, error) {
|
||||
// TODO: Implement touch command
|
||||
return CommandParams{}, nil
|
||||
}
|
||||
|
||||
func handleMkdir(cmd string) (CommandParams, error) {
|
||||
// TODO: Implement mkdir command
|
||||
return CommandParams{}, nil
|
||||
}
|
||||
|
||||
func handleCp(cmd string) (CommandParams, error) {
|
||||
// TODO: Implement cp command
|
||||
return CommandParams{}, nil
|
||||
}
|
||||
|
||||
func handleRm(cmd string) (CommandParams, error) {
|
||||
// TODO: Implement rm command
|
||||
return CommandParams{}, nil
|
||||
}
|
||||
|
||||
func handleRmdir(cmd string) (CommandParams, error) {
|
||||
// TODO: Implement rmdir command
|
||||
return CommandParams{}, nil
|
||||
}
|
||||
|
||||
func handleMv(cmd string) (CommandParams, error) {
|
||||
// TODO: Implement mv command
|
||||
return CommandParams{}, nil
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module github.com/linux-odyssey/linux-odyssey/services/cmd-hook
|
||||
|
||||
go 1.24.2
|
||||
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type CommandPayload struct {
|
||||
Command string `json:"command"`
|
||||
Output string `json:"output"`
|
||||
Error string `json:"error"`
|
||||
ExitCode string `json:"exit_code"`
|
||||
PWD string `json:"pwd"`
|
||||
Params CommandParams `json:"params"`
|
||||
}
|
||||
|
||||
func readOrNone(file string) string {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmdName := os.Getenv("CMD_NAME")
|
||||
if cmdName == "" {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
payload := CommandPayload{
|
||||
Command: cmdName,
|
||||
Output: readOrNone(os.Getenv("CMD_OUTPUT_FILE")),
|
||||
Error: readOrNone(os.Getenv("CMD_ERROR_FILE")),
|
||||
ExitCode: os.Getenv("CMD_EXIT_CODE"),
|
||||
PWD: os.Getenv("PWD"),
|
||||
}
|
||||
|
||||
params, err := handleCommand(cmdName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error handling command: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
payload.Params = params
|
||||
|
||||
apiEndpoint := os.Getenv("API_ENDPOINT")
|
||||
token := os.Getenv("TOKEN")
|
||||
|
||||
jsonData, err := json.MarshalIndent(payload, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error marshaling payload: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println(string(jsonData))
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/v1/commands", apiEndpoint), bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating request: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error sending request: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Fprintf(os.Stderr, "Failed to send command: %s\nResponse Body: %s\n", resp.Status, string(body))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FileType string
|
||||
|
||||
const (
|
||||
FileTypeFile FileType = "file"
|
||||
FileTypeDir FileType = "directory"
|
||||
)
|
||||
|
||||
func resolvePath(path string) string {
|
||||
if filepath.IsAbs(path) {
|
||||
return path
|
||||
}
|
||||
return filepath.Join(os.Getenv("PWD"), path)
|
||||
}
|
||||
|
||||
func fileExists(path string) (FileType, error) {
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
return FileTypeDir, nil
|
||||
}
|
||||
return FileTypeFile, nil
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
silent = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
@@ -0,0 +1,56 @@
|
||||
# Terminal Service
|
||||
|
||||
A lightweight terminal service for Linux Odyssey player containers, replacing SSH with a more efficient WebSocket-based solution.
|
||||
|
||||
## Features
|
||||
|
||||
- WebSocket-based terminal sessions
|
||||
- PTY (pseudo-terminal) management
|
||||
- Quest command execution
|
||||
- File status and content inspection
|
||||
- Direct integration with Traefik for frontend access
|
||||
|
||||
## Architecture
|
||||
|
||||
The service is designed to be lightweight and efficient, using:
|
||||
|
||||
- gorilla/websocket for WebSocket connections
|
||||
- creack/pty for terminal management
|
||||
- Traefik for reverse proxying
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.24 or later
|
||||
- Docker and Docker Compose
|
||||
- Traefik (for production deployment)
|
||||
|
||||
### Setup
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
go mod init github.com/linux-odyssey/linux-odyssey/terminal-service
|
||||
go get github.com/gorilla/websocket
|
||||
go get github.com/creack/pty
|
||||
```
|
||||
|
||||
2. Build and run:
|
||||
|
||||
```bash
|
||||
go build
|
||||
./terminal-service
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration details will be added as the service develops.
|
||||
|
||||
## Security
|
||||
|
||||
The service implements security measures to ensure safe container access and command execution.
|
||||
|
||||
## License
|
||||
|
||||
Same as the main Linux Odyssey project.
|
||||
@@ -0,0 +1,10 @@
|
||||
module github.com/linux-odyssey/linux-odyssey/services/terminal-service
|
||||
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/creack/pty v1.1.24 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
@@ -0,0 +1,82 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebSocket Terminal</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css"
|
||||
/>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
#terminal {
|
||||
width: 100%;
|
||||
height: calc(100vh - 40px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal"></div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
||||
<script>
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 14,
|
||||
fontFamily: 'monospace',
|
||||
theme: {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#ffffff',
|
||||
},
|
||||
})
|
||||
|
||||
const fitAddon = new FitAddon.FitAddon()
|
||||
term.loadAddon(fitAddon)
|
||||
|
||||
term.open(document.getElementById('terminal'))
|
||||
fitAddon.fit()
|
||||
|
||||
// Create WebSocket connection
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const ws = new WebSocket(`${protocol}//${window.location.host}/ws`)
|
||||
|
||||
// Handle WebSocket connection
|
||||
ws.onopen = () => {
|
||||
term.write('\r\n\x1B[1;32mConnected to terminal\x1B[0m\r\n')
|
||||
}
|
||||
|
||||
ws.onclose = () => {
|
||||
term.write('\r\n\x1B[1;31mDisconnected from terminal\x1B[0m\r\n')
|
||||
}
|
||||
|
||||
ws.onerror = (error) => {
|
||||
term.write(
|
||||
'\r\n\x1B[1;31mWebSocket error: ' + error.message + '\x1B[0m\r\n'
|
||||
)
|
||||
}
|
||||
|
||||
// Handle terminal input
|
||||
term.onData((data) => {
|
||||
ws.send(data)
|
||||
})
|
||||
|
||||
// Handle WebSocket messages
|
||||
ws.onmessage = (event) => {
|
||||
// Convert the data to a string if it's not already
|
||||
const data =
|
||||
typeof event.data === 'string'
|
||||
? event.data
|
||||
: new TextDecoder().decode(event.data)
|
||||
term.write(data)
|
||||
}
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', () => {
|
||||
fitAddon.fit()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,179 @@
|
||||
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))
|
||||
}
|
||||
Reference in new issue
Block a user