2023-10-17 06:31:13 +02:00
|
|
|
// Copyright 2023, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2022-06-12 22:39:48 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-07-01 21:17:19 +02:00
|
|
|
"context"
|
2023-09-01 07:04:31 +02:00
|
|
|
"encoding/base64"
|
2022-06-12 22:39:48 +02:00
|
|
|
"encoding/json"
|
2022-06-17 07:22:47 +02:00
|
|
|
"errors"
|
2022-06-12 22:39:48 +02:00
|
|
|
"fmt"
|
2023-12-23 02:55:20 +01:00
|
|
|
"html"
|
2023-09-01 07:04:31 +02:00
|
|
|
"io"
|
2022-06-17 07:22:47 +02:00
|
|
|
"io/fs"
|
2022-10-31 20:40:45 +01:00
|
|
|
"log"
|
2023-09-01 07:04:31 +02:00
|
|
|
"mime/multipart"
|
2022-06-12 22:39:48 +02:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2023-03-12 23:16:49 +01:00
|
|
|
"os/signal"
|
2023-09-01 07:04:31 +02:00
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
2023-11-10 01:26:44 +01:00
|
|
|
"runtime"
|
2022-07-15 10:57:45 +02:00
|
|
|
"runtime/debug"
|
2022-09-04 08:57:05 +02:00
|
|
|
"strconv"
|
2022-06-13 20:11:56 +02:00
|
|
|
"strings"
|
2022-06-15 07:39:09 +02:00
|
|
|
"sync"
|
2022-10-30 21:05:53 +01:00
|
|
|
"syscall"
|
2022-06-12 22:39:48 +02:00
|
|
|
"time"
|
|
|
|
|
2022-06-13 20:11:56 +02:00
|
|
|
"github.com/google/uuid"
|
2022-06-12 22:39:48 +02:00
|
|
|
"github.com/gorilla/mux"
|
|
|
|
|
zsh support (#227)
adds zsh support to waveterm. big change, lots going on here. lots of other improvements and bug fixes added while debugging and building out the feature.
Commits:
* refactor shexec parser.go into new package shellenv. separate out bash specific parsing from generic functions
* checkpoint
* work on refactoring shexec. created two new packages shellapi (for bash/zsh specific stuff), and shellutil (shared between shellapi and shexec)
* more refactoring
* create shellapi interface to abstract bash specific functionality
* more refactoring, move bash shell state parsing to shellapi
* move makeRcFile to shellapi. remove all of the 'client' options CLI options from waveshell
* get shellType passed through to server/single paths for waveshell
* add a local shelltype detector
* mock out a zshapi
* move shelltype through more of the code
* get a command to run via zsh
* zsh can now switch directories. poc, needs cleanup
* working on ShellState encoding differences between zsh/bash. Working on parsing zsh decls. move utilfn package into waveshell (shouldn't have been in wavesrv)
* switch to use []byte for vardecl serialization + diffs
* progress on zsh environment. still have issues reconciling init environment with trap environment
* fix typeset argument parsing
* parse promptvars, more zsh specific ignores
* fix bug with promptvar not getting set (wrong check in FeState func)
* add sdk (issue #188) to list of rtnstate commands
* more zsh compatibility -- working with a larger ohmyzsh environment. ignore more variables, handle exit trap better. unique path/fpath. add a processtype variable to base.
* must return a value
* zsh alias parsing/restoring. diff changes (and rtnstate changes). introduces linediff v1.
* force zmodload of zsh/parameter
* starting work on zsh functions
* need a v1 of mapdiff as well (to handle null chars)
* pack/unpack of ints was wrong (one used int and one use uint). turned out we only ever encoded '0' so it worked. that also means it is safe to change unpack to unpackUInt
* reworking for binary encoding of aliases and functions (because of zsh allows any character, including nulls, in names and values)
* fixes, working on functions, issue with line endings
* zsh functions. lots of ugliness here around dealing with line dicipline and cooked stty. new runcommand function to grab output from a non-tty fd. note that we still to run the actual command in a stty to get the proper output.
* write uuid tempdir, cleanup with tmprcfilename code
* hack in some simple zsh function declaration finding code for rtnstate. create function diff for rtnstate that supports zsh
* make sure key order is constant so shell hashes are consistent
* fix problems with state diffs to support new zsh formats. add diff/apply code to shellapi (moved from shellenv), that is now specific to zsh or bash
* add log packet and new shellstate packets
* switch to shellstate map that's also keyed by shelltype
* add shelltype to remoteinstance
* remove shell argument from waveshell
* added new shelltype statemap to remote.go (msh), deal with fallout
* move shellstate out of init packet, and move to an explicit reinit call. try to initialize all of the active shell states
* change dont always store init state (only store on demand). initialize shell states on demand (if not already initialized). allow reset to change shells
* add shellpref field to remote table. use to drive the default shell choice for new tabs
* show shelltag on cmdinput, pass through ri and remote (defaultshellstate)
* bump mshell version to v0.4
* better version validation for shellstate. also relax compatibility requirements for diffing states (shelltype + major version need to match)
* better error handling, check shellstate compatibility during run (on waveshell server)
* add extra separator for bash shellstate processing to deal with spurious output from rc files
* special migration for v30 -- flag invalid bash shell states and show special button in UI to fix
* format
* remove zsh-decls (unused)
* remove test code
* remove debug print
* fix typo
2024-01-17 01:11:04 +01:00
|
|
|
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
|
2023-10-16 22:30:10 +02:00
|
|
|
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
|
|
|
|
"github.com/wavetermdev/waveterm/waveshell/pkg/server"
|
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/cmdrunner"
|
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud"
|
2023-12-16 02:43:54 +01:00
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker"
|
2023-10-16 22:30:10 +02:00
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
|
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/rtnstate"
|
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scws"
|
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
|
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/wsshell"
|
2022-06-12 22:39:48 +02:00
|
|
|
)
|
|
|
|
|
2022-12-21 01:16:46 +01:00
|
|
|
type WebFnType = func(http.ResponseWriter, *http.Request)
|
|
|
|
|
2022-06-12 22:39:48 +02:00
|
|
|
const HttpReadTimeout = 5 * time.Second
|
|
|
|
const HttpWriteTimeout = 21 * time.Second
|
|
|
|
const HttpMaxHeaderBytes = 60000
|
|
|
|
const HttpTimeoutDuration = 21 * time.Second
|
|
|
|
|
2023-11-01 09:26:19 +01:00
|
|
|
const MainServerAddr = "127.0.0.1:1619" // wavesrv, P=16, S=19, PS=1619
|
|
|
|
const WebSocketServerAddr = "127.0.0.1:1623" // wavesrv:websocket, P=16, W=23, PW=1623
|
2023-08-15 02:04:16 +02:00
|
|
|
const MainServerDevAddr = "127.0.0.1:8090"
|
|
|
|
const WebSocketServerDevAddr = "127.0.0.1:8091"
|
2022-06-17 07:22:47 +02:00
|
|
|
const WSStateReconnectTime = 30 * time.Second
|
|
|
|
const WSStatePacketChSize = 20
|
2022-06-16 03:13:16 +02:00
|
|
|
|
2023-01-26 07:49:50 +01:00
|
|
|
const InitialTelemetryWait = 30 * time.Second
|
|
|
|
const TelemetryTick = 30 * time.Minute
|
|
|
|
const TelemetryInterval = 8 * time.Hour
|
|
|
|
|
2023-09-01 07:04:31 +02:00
|
|
|
const MaxWriteFileMemSize = 20 * (1024 * 1024) // 20M
|
|
|
|
|
2022-06-17 07:22:47 +02:00
|
|
|
var GlobalLock = &sync.Mutex{}
|
2022-07-13 23:16:08 +02:00
|
|
|
var WSStateMap = make(map[string]*scws.WSState) // clientid -> WsState
|
2022-12-21 01:16:46 +01:00
|
|
|
var GlobalAuthKey string
|
2023-02-24 00:17:47 +01:00
|
|
|
var BuildTime = "0"
|
2023-03-12 23:24:53 +01:00
|
|
|
var shutdownOnce sync.Once
|
2023-09-01 07:04:31 +02:00
|
|
|
var ContentTypeHeaderValidRe = regexp.MustCompile(`^\w+/[\w.+-]+$`)
|
2022-06-12 22:39:48 +02:00
|
|
|
|
2023-01-17 08:36:52 +01:00
|
|
|
type ClientActiveState struct {
|
|
|
|
Fg bool `json:"fg"`
|
|
|
|
Active bool `json:"active"`
|
|
|
|
Open bool `json:"open"`
|
|
|
|
}
|
|
|
|
|
2023-12-23 02:55:20 +01:00
|
|
|
// Error constants
|
|
|
|
const (
|
|
|
|
ErrorDecodingJson = "error decoding json: %w"
|
|
|
|
ErrorPanic = "panic: %v"
|
|
|
|
ErrorInvalidScreenId = "invalid screenid: %v"
|
|
|
|
ErrorInvalidLineId = "invalid lineid: %v"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Header constants
|
|
|
|
const (
|
|
|
|
CacheControlHeaderKey = "Cache-Control"
|
|
|
|
CacheControlHeaderNoCache = "no-cache"
|
|
|
|
ContentTypeHeaderKey = "Content-Type"
|
|
|
|
ContentTypeJson = "application/json"
|
|
|
|
)
|
|
|
|
|
2022-07-13 23:16:08 +02:00
|
|
|
func setWSState(state *scws.WSState) {
|
2022-06-17 07:22:47 +02:00
|
|
|
GlobalLock.Lock()
|
|
|
|
defer GlobalLock.Unlock()
|
|
|
|
WSStateMap[state.ClientId] = state
|
2022-06-12 22:39:48 +02:00
|
|
|
}
|
|
|
|
|
2022-07-13 23:16:08 +02:00
|
|
|
func getWSState(clientId string) *scws.WSState {
|
2022-06-17 07:22:47 +02:00
|
|
|
GlobalLock.Lock()
|
|
|
|
defer GlobalLock.Unlock()
|
|
|
|
return WSStateMap[clientId]
|
|
|
|
}
|
|
|
|
|
|
|
|
func removeWSStateAfterTimeout(clientId string, connectTime time.Time, waitDuration time.Duration) {
|
|
|
|
go func() {
|
|
|
|
time.Sleep(waitDuration)
|
|
|
|
GlobalLock.Lock()
|
|
|
|
defer GlobalLock.Unlock()
|
|
|
|
state := WSStateMap[clientId]
|
|
|
|
if state == nil || state.ConnectTime != connectTime {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
delete(WSStateMap, clientId)
|
2022-07-13 23:16:08 +02:00
|
|
|
state.UnWatchScreen()
|
2022-06-17 07:22:47 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2022-06-12 22:39:48 +02:00
|
|
|
func HandleWs(w http.ResponseWriter, r *http.Request) {
|
|
|
|
shell, err := wsshell.StartWS(w, r)
|
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("WebSocket Upgrade Failed %T: %v\n", w, err)
|
2022-06-12 22:39:48 +02:00
|
|
|
return
|
|
|
|
}
|
2022-06-17 07:22:47 +02:00
|
|
|
defer shell.Conn.Close()
|
|
|
|
clientId := r.URL.Query().Get("clientid")
|
|
|
|
if clientId == "" {
|
|
|
|
close(shell.WriteChan)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
state := getWSState(clientId)
|
|
|
|
if state == nil {
|
2022-12-21 01:16:46 +01:00
|
|
|
state = scws.MakeWSState(clientId, GlobalAuthKey)
|
2022-07-13 23:16:08 +02:00
|
|
|
state.ReplaceShell(shell)
|
2022-06-17 07:22:47 +02:00
|
|
|
setWSState(state)
|
|
|
|
} else {
|
2022-07-13 23:16:08 +02:00
|
|
|
state.UpdateConnectTime()
|
|
|
|
state.ReplaceShell(shell)
|
2022-06-17 07:22:47 +02:00
|
|
|
}
|
2022-07-13 23:16:08 +02:00
|
|
|
stateConnectTime := state.GetConnectTime()
|
2022-06-15 07:39:09 +02:00
|
|
|
defer func() {
|
2022-06-17 07:22:47 +02:00
|
|
|
removeWSStateAfterTimeout(clientId, stateConnectTime, WSStateReconnectTime)
|
2022-06-12 22:39:48 +02:00
|
|
|
}()
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("WebSocket opened %s %s\n", state.ClientId, shell.RemoteAddr)
|
2022-07-13 23:16:08 +02:00
|
|
|
state.RunWSRead()
|
2022-06-12 22:39:48 +02:00
|
|
|
}
|
|
|
|
|
2022-06-18 00:30:42 +02:00
|
|
|
// todo: sync multiple writes to the same fifoName into a single go-routine and do liveness checking on fifo
|
|
|
|
// if this returns an error, likely the fifo is dead and the cmd should be marked as 'done'
|
|
|
|
func writeToFifo(fifoName string, data []byte) error {
|
|
|
|
rwfd, err := os.OpenFile(fifoName, os.O_RDWR, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer rwfd.Close()
|
|
|
|
fifoWriter, err := os.OpenFile(fifoName, os.O_WRONLY, 0600) // blocking open (open won't block because of rwfd)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fifoWriter.Close()
|
|
|
|
// this *could* block if the fifo buffer is full
|
|
|
|
// unlikely because if the reader is dead, and len(data) < pipe size, then the buffer will be empty and will clear after rwfd is closed
|
|
|
|
_, err = fifoWriter.Write(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-09-25 09:26:33 +02:00
|
|
|
func HandleGetClientData(w http.ResponseWriter, r *http.Request) {
|
|
|
|
cdata, err := sstore.EnsureClientData(r.Context())
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2023-05-09 01:06:51 +02:00
|
|
|
cdata = cdata.Clean()
|
2022-09-25 09:26:33 +02:00
|
|
|
WriteJsonSuccess(w, cdata)
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandleSetWinSize(w http.ResponseWriter, r *http.Request) {
|
|
|
|
decoder := json.NewDecoder(r.Body)
|
|
|
|
var winSize sstore.ClientWinSizeType
|
|
|
|
err := decoder.Decode(&winSize)
|
|
|
|
if err != nil {
|
2023-12-23 02:55:20 +01:00
|
|
|
WriteJsonError(w, fmt.Errorf(ErrorDecodingJson, err))
|
2022-09-25 09:26:33 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
err = sstore.SetWinSize(r.Context(), winSize)
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error setting winsize: %w", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
WriteJsonSuccess(w, true)
|
|
|
|
}
|
|
|
|
|
2023-01-17 08:36:52 +01:00
|
|
|
// params: fg, active, open
|
|
|
|
func HandleLogActiveState(w http.ResponseWriter, r *http.Request) {
|
|
|
|
decoder := json.NewDecoder(r.Body)
|
|
|
|
var activeState ClientActiveState
|
|
|
|
err := decoder.Decode(&activeState)
|
|
|
|
if err != nil {
|
2023-12-23 02:55:20 +01:00
|
|
|
WriteJsonError(w, fmt.Errorf(ErrorDecodingJson, err))
|
2023-01-17 08:36:52 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
activity := sstore.ActivityUpdate{}
|
|
|
|
if activeState.Fg {
|
|
|
|
activity.FgMinutes = 1
|
|
|
|
}
|
|
|
|
if activeState.Active {
|
|
|
|
activity.ActiveMinutes = 1
|
|
|
|
}
|
|
|
|
if activeState.Open {
|
|
|
|
activity.OpenMinutes = 1
|
|
|
|
}
|
2023-02-22 07:41:56 +01:00
|
|
|
activity.NumConns = remote.NumRemotes()
|
2023-01-17 08:36:52 +01:00
|
|
|
err = sstore.UpdateCurrentActivity(r.Context(), activity)
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error updating activity: %w", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
WriteJsonSuccess(w, true)
|
|
|
|
}
|
|
|
|
|
2023-03-13 09:52:30 +01:00
|
|
|
// params: screenid
|
2023-03-13 18:50:29 +01:00
|
|
|
func HandleGetScreenLines(w http.ResponseWriter, r *http.Request) {
|
2022-07-02 22:31:56 +02:00
|
|
|
qvals := r.URL.Query()
|
2023-03-13 09:52:30 +01:00
|
|
|
screenId := qvals.Get("screenid")
|
|
|
|
if _, err := uuid.Parse(screenId); err != nil {
|
2023-12-23 02:55:20 +01:00
|
|
|
WriteJsonError(w, fmt.Errorf("invalid screenid, err: %w", err))
|
2022-07-02 22:31:56 +02:00
|
|
|
return
|
|
|
|
}
|
2023-03-13 18:50:29 +01:00
|
|
|
screenLines, err := sstore.GetScreenLinesById(r.Context(), screenId)
|
2022-07-02 22:31:56 +02:00
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2023-03-13 20:10:23 +01:00
|
|
|
WriteJsonSuccess(w, screenLines)
|
2022-07-02 22:31:56 +02:00
|
|
|
}
|
|
|
|
|
2022-10-27 09:33:50 +02:00
|
|
|
func HandleRtnState(w http.ResponseWriter, r *http.Request) {
|
2022-11-29 03:03:02 +01:00
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("[error] in handlertnstate: %v\n", r)
|
|
|
|
debug.PrintStack()
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte(fmt.Sprintf(ErrorPanic, r)))
|
2022-11-29 03:03:02 +01:00
|
|
|
}()
|
2022-10-27 09:33:50 +02:00
|
|
|
qvals := r.URL.Query()
|
2023-03-21 03:20:57 +01:00
|
|
|
screenId := qvals.Get("screenid")
|
2023-07-31 03:32:56 +02:00
|
|
|
lineId := qvals.Get("lineid")
|
|
|
|
if screenId == "" || lineId == "" {
|
2022-10-27 09:33:50 +02:00
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("must specify screenid and lineid"))
|
2022-10-27 09:33:50 +02:00
|
|
|
return
|
|
|
|
}
|
2023-03-21 03:20:57 +01:00
|
|
|
if _, err := uuid.Parse(screenId); err != nil {
|
2022-10-27 09:33:50 +02:00
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte(fmt.Sprintf(ErrorInvalidScreenId, err)))
|
2022-10-27 09:33:50 +02:00
|
|
|
return
|
|
|
|
}
|
2023-07-31 03:32:56 +02:00
|
|
|
if _, err := uuid.Parse(lineId); err != nil {
|
2022-10-27 09:33:50 +02:00
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte(fmt.Sprintf(ErrorInvalidLineId, err)))
|
2022-10-27 09:33:50 +02:00
|
|
|
return
|
|
|
|
}
|
2023-07-31 03:32:56 +02:00
|
|
|
data, err := rtnstate.GetRtnStateDiff(r.Context(), screenId, lineId)
|
2022-10-27 09:33:50 +02:00
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte(fmt.Sprintf("cannot get rtnstate diff: %v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(data)
|
|
|
|
}
|
|
|
|
|
2022-09-15 08:10:35 +02:00
|
|
|
func HandleRemotePty(w http.ResponseWriter, r *http.Request) {
|
|
|
|
qvals := r.URL.Query()
|
|
|
|
remoteId := qvals.Get("remoteid")
|
|
|
|
if remoteId == "" {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("must specify remoteid"))
|
2022-09-15 08:10:35 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err := uuid.Parse(remoteId); err != nil {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte(fmt.Sprintf("invalid remoteid: %v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
realOffset, data, err := remote.ReadRemotePty(r.Context(), remoteId)
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte(fmt.Sprintf("error reading ptyout file: %v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Header().Set("X-PtyDataOffset", strconv.FormatInt(realOffset, 10))
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(data)
|
|
|
|
}
|
|
|
|
|
2022-07-03 21:01:56 +02:00
|
|
|
func HandleGetPtyOut(w http.ResponseWriter, r *http.Request) {
|
2022-06-12 22:39:48 +02:00
|
|
|
qvals := r.URL.Query()
|
2023-03-21 03:20:57 +01:00
|
|
|
screenId := qvals.Get("screenid")
|
2023-07-31 03:32:56 +02:00
|
|
|
lineId := qvals.Get("lineid")
|
|
|
|
if screenId == "" || lineId == "" {
|
2022-06-12 22:39:48 +02:00
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("must specify screenid and lineid"))
|
2022-06-12 22:39:48 +02:00
|
|
|
return
|
|
|
|
}
|
2023-03-21 03:20:57 +01:00
|
|
|
if _, err := uuid.Parse(screenId); err != nil {
|
2022-07-07 07:46:22 +02:00
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte(fmt.Sprintf(ErrorInvalidScreenId, err)))
|
2022-07-07 07:46:22 +02:00
|
|
|
return
|
|
|
|
}
|
2023-07-31 03:32:56 +02:00
|
|
|
if _, err := uuid.Parse(lineId); err != nil {
|
2022-07-07 07:46:22 +02:00
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte(fmt.Sprintf(ErrorInvalidLineId, err)))
|
2022-07-07 07:46:22 +02:00
|
|
|
return
|
|
|
|
}
|
2023-07-31 03:32:56 +02:00
|
|
|
realOffset, data, err := sstore.ReadFullPtyOutFile(r.Context(), screenId, lineId)
|
2022-06-12 22:39:48 +02:00
|
|
|
if err != nil {
|
2022-06-17 07:22:47 +02:00
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
return
|
|
|
|
}
|
2022-06-12 22:39:48 +02:00
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte(html.EscapeString(fmt.Sprintf("error reading ptyout file: %v", err))))
|
2022-06-12 22:39:48 +02:00
|
|
|
return
|
|
|
|
}
|
2022-09-04 08:57:05 +02:00
|
|
|
w.Header().Set("X-PtyDataOffset", strconv.FormatInt(realOffset, 10))
|
2022-06-12 22:39:48 +02:00
|
|
|
w.WriteHeader(http.StatusOK)
|
2022-08-19 22:23:00 +02:00
|
|
|
w.Write(data)
|
2022-06-12 22:39:48 +02:00
|
|
|
}
|
|
|
|
|
2023-09-01 07:04:31 +02:00
|
|
|
type writeFileParamsType struct {
|
|
|
|
ScreenId string `json:"screenid"`
|
|
|
|
LineId string `json:"lineid"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
UseTemp bool `json:"usetemp,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseWriteFileParams(r *http.Request) (*writeFileParamsType, multipart.File, error) {
|
|
|
|
err := r.ParseMultipartForm(MaxWriteFileMemSize)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("cannot parse multipart form data: %v", err)
|
|
|
|
}
|
|
|
|
form := r.MultipartForm
|
|
|
|
if len(form.Value["params"]) == 0 {
|
|
|
|
return nil, nil, fmt.Errorf("no params found")
|
|
|
|
}
|
|
|
|
paramsStr := form.Value["params"][0]
|
|
|
|
var params writeFileParamsType
|
|
|
|
err = json.Unmarshal([]byte(paramsStr), ¶ms)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("bad params json: %v", err)
|
|
|
|
}
|
|
|
|
if len(form.File["data"]) == 0 {
|
|
|
|
return nil, nil, fmt.Errorf("no data found")
|
|
|
|
}
|
|
|
|
fileHeader := form.File["data"][0]
|
|
|
|
file, err := fileHeader.Open()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("error opening multipart data file: %v", err)
|
|
|
|
}
|
|
|
|
return ¶ms, file, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("[error] in write-file: %v\n", r)
|
|
|
|
debug.PrintStack()
|
2023-12-23 02:55:20 +01:00
|
|
|
WriteJsonError(w, fmt.Errorf(ErrorPanic, r))
|
2023-09-01 07:04:31 +02:00
|
|
|
}()
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Header().Set(CacheControlHeaderKey, CacheControlHeaderNoCache)
|
2023-09-01 07:04:31 +02:00
|
|
|
params, mpFile, err := parseWriteFileParams(r)
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error parsing multipart form params: %w", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if params.ScreenId == "" || params.LineId == "" || params.Path == "" {
|
|
|
|
WriteJsonError(w, fmt.Errorf("invalid params, must set screenid, lineid, and path"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err := uuid.Parse(params.ScreenId); err != nil {
|
2023-12-23 02:55:20 +01:00
|
|
|
WriteJsonError(w, fmt.Errorf(ErrorInvalidScreenId, err))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err := uuid.Parse(params.LineId); err != nil {
|
2023-12-23 02:55:20 +01:00
|
|
|
WriteJsonError(w, fmt.Errorf(ErrorInvalidLineId, err))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
_, cmd, err := sstore.GetLineCmdByLineId(r.Context(), params.ScreenId, params.LineId)
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("cannot retrieve line/cmd: %v", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if cmd == nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("line not found"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if cmd.Remote.RemoteId == "" {
|
|
|
|
WriteJsonError(w, fmt.Errorf("invalid line, no remote"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
msh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
|
|
|
if msh == nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("invalid line, cannot resolve remote"))
|
|
|
|
return
|
|
|
|
}
|
2023-09-02 05:40:28 +02:00
|
|
|
rrState := msh.GetRemoteRuntimeState()
|
|
|
|
fullPath, err := rrState.ExpandHomeDir(params.Path)
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error expanding homedir: %v", err))
|
|
|
|
return
|
|
|
|
}
|
2023-09-01 07:04:31 +02:00
|
|
|
cwd := cmd.FeState["cwd"]
|
|
|
|
writePk := packet.MakeWriteFilePacket()
|
|
|
|
writePk.ReqId = uuid.New().String()
|
|
|
|
writePk.UseTemp = params.UseTemp
|
2023-09-02 05:40:28 +02:00
|
|
|
if filepath.IsAbs(fullPath) {
|
|
|
|
writePk.Path = fullPath
|
2023-09-01 07:04:31 +02:00
|
|
|
} else {
|
2023-09-02 05:40:28 +02:00
|
|
|
writePk.Path = filepath.Join(cwd, fullPath)
|
2023-09-01 07:04:31 +02:00
|
|
|
}
|
|
|
|
iter, err := msh.PacketRpcIter(r.Context(), writePk)
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error: %v", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// first packet should be WriteFileReady
|
|
|
|
readyIf, err := iter.Next(r.Context())
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error while getting ready response: %w", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
readyPk, ok := readyIf.(*packet.WriteFileReadyPacketType)
|
|
|
|
if !ok {
|
|
|
|
WriteJsonError(w, fmt.Errorf("bad ready packet received: %T", readyIf))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if readyPk.Error != "" {
|
|
|
|
WriteJsonError(w, fmt.Errorf("ready error: %s", readyPk.Error))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var buffer [server.MaxFileDataPacketSize]byte
|
|
|
|
bufSlice := buffer[:]
|
|
|
|
for {
|
|
|
|
dataPk := packet.MakeFileDataPacket(writePk.ReqId)
|
|
|
|
nr, err := io.ReadFull(mpFile, bufSlice)
|
|
|
|
if err == io.ErrUnexpectedEOF || err == io.EOF {
|
|
|
|
dataPk.Eof = true
|
|
|
|
} else if err != nil {
|
|
|
|
dataErr := fmt.Errorf("error reading file data: %v", err)
|
|
|
|
dataPk.Error = dataErr.Error()
|
|
|
|
msh.SendFileData(dataPk)
|
|
|
|
WriteJsonError(w, dataErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if nr > 0 {
|
|
|
|
dataPk.Data = make([]byte, nr)
|
|
|
|
copy(dataPk.Data, bufSlice[0:nr])
|
|
|
|
}
|
|
|
|
msh.SendFileData(dataPk)
|
|
|
|
if dataPk.Eof {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
// slight throttle for sending packets
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
}
|
|
|
|
doneIf, err := iter.Next(r.Context())
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error while getting done response: %w", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
donePk, ok := doneIf.(*packet.WriteFileDonePacketType)
|
|
|
|
if !ok {
|
|
|
|
WriteJsonError(w, fmt.Errorf("bad done packet received: %T", doneIf))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if donePk.Error != "" {
|
|
|
|
WriteJsonError(w, fmt.Errorf("dne error: %s", donePk.Error))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
WriteJsonSuccess(w, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandleReadFile(w http.ResponseWriter, r *http.Request) {
|
|
|
|
qvals := r.URL.Query()
|
|
|
|
screenId := qvals.Get("screenid")
|
|
|
|
lineId := qvals.Get("lineid")
|
|
|
|
path := qvals.Get("path") // validate path?
|
|
|
|
contentType := qvals.Get("mimetype")
|
|
|
|
if contentType == "" {
|
|
|
|
contentType = "application/octet-stream"
|
|
|
|
}
|
|
|
|
if screenId == "" || lineId == "" {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("must specify sessionid, screenid, and lineid"))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if path == "" {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("must specify path"))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err := uuid.Parse(screenId); err != nil {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte(fmt.Sprintf(ErrorInvalidScreenId, err)))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err := uuid.Parse(lineId); err != nil {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte(fmt.Sprintf(ErrorInvalidLineId, err)))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if !ContentTypeHeaderValidRe.MatchString(contentType) {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("invalid mimetype specified"))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
_, cmd, err := sstore.GetLineCmdByLineId(r.Context(), screenId, lineId)
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte(fmt.Sprintf("invalid lineid: %v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if cmd == nil {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("invalid line, no cmd"))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if cmd.Remote.RemoteId == "" {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("invalid line, no remote"))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
msh := remote.GetRemoteById(cmd.Remote.RemoteId)
|
|
|
|
if msh == nil {
|
|
|
|
w.WriteHeader(500)
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Write([]byte("invalid line, cannot resolve remote"))
|
2023-09-01 07:04:31 +02:00
|
|
|
return
|
|
|
|
}
|
2023-09-02 05:40:28 +02:00
|
|
|
rrState := msh.GetRemoteRuntimeState()
|
|
|
|
fullPath, err := rrState.ExpandHomeDir(path)
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error expanding homedir: %v", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
streamPk := packet.MakeStreamFilePacket()
|
|
|
|
streamPk.ReqId = uuid.New().String()
|
|
|
|
cwd := cmd.FeState["cwd"]
|
|
|
|
if filepath.IsAbs(fullPath) {
|
|
|
|
streamPk.Path = fullPath
|
|
|
|
} else {
|
|
|
|
streamPk.Path = filepath.Join(cwd, fullPath)
|
|
|
|
}
|
2023-09-01 07:04:31 +02:00
|
|
|
iter, err := msh.StreamFile(r.Context(), streamPk)
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte(fmt.Sprintf("error trying to stream file: %v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer iter.Close()
|
|
|
|
respIf, err := iter.Next(r.Context())
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte(fmt.Sprintf("error getting streamfile response: %v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
resp, ok := respIf.(*packet.StreamFileResponseType)
|
|
|
|
if !ok {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte(fmt.Sprintf("bad response packet type: %T", respIf)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if resp.Error != "" {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte(fmt.Sprintf("error response: %s", resp.Error)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
infoJson, _ := json.Marshal(resp.Info)
|
|
|
|
w.Header().Set("X-FileInfo", base64.StdEncoding.EncodeToString(infoJson))
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Header().Set(ContentTypeHeaderKey, contentType)
|
2023-09-01 07:04:31 +02:00
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
for {
|
|
|
|
dataPkIf, err := iter.Next(r.Context())
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error in read-file while getting data: %v\n", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if dataPkIf == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
dataPk, ok := dataPkIf.(*packet.FileDataPacketType)
|
|
|
|
if !ok {
|
|
|
|
log.Printf("error in read-file, invalid data packet type: %T", dataPkIf)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if dataPk.Error != "" {
|
|
|
|
log.Printf("in read-file, data packet error: %s", dataPk.Error)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
w.Write(dataPk.Data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-12 22:39:48 +02:00
|
|
|
func WriteJsonError(w http.ResponseWriter, errVal error) {
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Header().Set(ContentTypeHeaderKey, ContentTypeJson)
|
2022-07-16 02:37:32 +02:00
|
|
|
w.WriteHeader(200)
|
2022-06-12 22:39:48 +02:00
|
|
|
errMap := make(map[string]interface{})
|
|
|
|
errMap["error"] = errVal.Error()
|
|
|
|
barr, _ := json.Marshal(errMap)
|
|
|
|
w.Write(barr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func WriteJsonSuccess(w http.ResponseWriter, data interface{}) {
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Header().Set(ContentTypeHeaderKey, ContentTypeJson)
|
2022-06-12 22:39:48 +02:00
|
|
|
rtnMap := make(map[string]interface{})
|
|
|
|
rtnMap["success"] = true
|
|
|
|
if data != nil {
|
|
|
|
rtnMap["data"] = data
|
|
|
|
}
|
|
|
|
barr, err := json.Marshal(rtnMap)
|
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.WriteHeader(200)
|
|
|
|
w.Write(barr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandleRunCommand(w http.ResponseWriter, r *http.Request) {
|
2022-07-15 10:57:45 +02:00
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r == nil {
|
|
|
|
return
|
|
|
|
}
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] in run-command: %v\n", r)
|
2022-07-15 10:57:45 +02:00
|
|
|
debug.PrintStack()
|
2023-12-23 02:55:20 +01:00
|
|
|
WriteJsonError(w, fmt.Errorf(ErrorPanic, r))
|
2022-07-15 10:57:45 +02:00
|
|
|
}()
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Header().Set(CacheControlHeaderKey, CacheControlHeaderNoCache)
|
2022-06-12 22:39:48 +02:00
|
|
|
decoder := json.NewDecoder(r.Body)
|
2022-07-02 22:31:56 +02:00
|
|
|
var commandPk scpacket.FeCommandPacketType
|
|
|
|
err := decoder.Decode(&commandPk)
|
2022-06-12 22:39:48 +02:00
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, fmt.Errorf("error decoding json: %w", err))
|
|
|
|
return
|
|
|
|
}
|
2022-07-16 02:37:32 +02:00
|
|
|
update, err := cmdrunner.HandleCommand(r.Context(), &commandPk)
|
2022-07-03 21:01:56 +02:00
|
|
|
if err != nil {
|
|
|
|
WriteJsonError(w, err)
|
2022-06-13 20:11:56 +02:00
|
|
|
return
|
|
|
|
}
|
2023-05-09 01:06:51 +02:00
|
|
|
if update != nil {
|
|
|
|
update.Clean()
|
|
|
|
}
|
2022-07-16 02:37:32 +02:00
|
|
|
WriteJsonSuccess(w, update)
|
2022-07-03 21:01:56 +02:00
|
|
|
}
|
|
|
|
|
2022-12-21 01:16:46 +01:00
|
|
|
func AuthKeyWrap(fn WebFnType) WebFnType {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
reqAuthKey := r.Header.Get("X-AuthKey")
|
|
|
|
if reqAuthKey == "" {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte("no x-authkey header"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if reqAuthKey != GlobalAuthKey {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
w.Write([]byte("x-authkey header is invalid"))
|
|
|
|
return
|
|
|
|
}
|
2023-12-23 02:55:20 +01:00
|
|
|
w.Header().Set(CacheControlHeaderKey, CacheControlHeaderNoCache)
|
2022-12-21 01:16:46 +01:00
|
|
|
fn(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-16 03:13:16 +02:00
|
|
|
func runWebSocketServer() {
|
|
|
|
gr := mux.NewRouter()
|
|
|
|
gr.HandleFunc("/ws", HandleWs)
|
2022-12-29 02:47:12 +01:00
|
|
|
serverAddr := WebSocketServerAddr
|
|
|
|
if scbase.IsDevMode() {
|
|
|
|
serverAddr = WebSocketServerDevAddr
|
|
|
|
}
|
2022-06-16 03:13:16 +02:00
|
|
|
server := &http.Server{
|
2022-12-29 02:47:12 +01:00
|
|
|
Addr: serverAddr,
|
2022-06-16 03:13:16 +02:00
|
|
|
ReadTimeout: HttpReadTimeout,
|
|
|
|
WriteTimeout: HttpWriteTimeout,
|
|
|
|
MaxHeaderBytes: HttpMaxHeaderBytes,
|
|
|
|
Handler: gr,
|
|
|
|
}
|
|
|
|
server.SetKeepAlivesEnabled(false)
|
2022-12-29 02:47:12 +01:00
|
|
|
log.Printf("Running websocket server on %s\n", serverAddr)
|
2022-06-16 03:13:16 +02:00
|
|
|
err := server.ListenAndServe()
|
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] trying to run websocket server: %v\n", err)
|
2022-06-16 03:13:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-06 01:31:22 +02:00
|
|
|
func test() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-23 08:10:18 +01:00
|
|
|
func sendTelemetryWrapper() {
|
2023-01-23 21:54:32 +01:00
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("[error] in sendTelemetryWrapper: %v\n", r)
|
|
|
|
debug.PrintStack()
|
|
|
|
}()
|
2023-01-23 08:10:18 +01:00
|
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancelFn()
|
2023-01-23 21:54:32 +01:00
|
|
|
err := pcloud.SendTelemetry(ctx, false)
|
2023-01-23 08:10:18 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("[error] sending telemetry: %v\n", err)
|
|
|
|
}
|
2023-01-18 01:02:44 +01:00
|
|
|
}
|
|
|
|
|
2023-12-16 02:43:54 +01:00
|
|
|
func checkNewReleaseWrapper() {
|
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("[error] in checkNewReleaseWrapper: %v\n", r)
|
|
|
|
debug.PrintStack()
|
|
|
|
}()
|
|
|
|
|
|
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancelFn()
|
|
|
|
|
|
|
|
_, err := releasechecker.CheckNewRelease(ctx, false)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("[error] checking for new release: %v\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-26 07:49:50 +01:00
|
|
|
func telemetryLoop() {
|
|
|
|
var lastSent time.Time
|
|
|
|
time.Sleep(InitialTelemetryWait)
|
|
|
|
for {
|
2023-12-16 02:43:54 +01:00
|
|
|
dur := time.Since(lastSent)
|
2023-01-26 07:49:50 +01:00
|
|
|
if lastSent.IsZero() || dur >= TelemetryInterval {
|
|
|
|
lastSent = time.Now()
|
|
|
|
sendTelemetryWrapper()
|
2023-12-16 02:43:54 +01:00
|
|
|
checkNewReleaseWrapper()
|
2023-01-26 07:49:50 +01:00
|
|
|
}
|
|
|
|
time.Sleep(TelemetryTick)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-30 21:05:53 +01:00
|
|
|
// watch stdin, kill server if stdin is closed
|
|
|
|
func stdinReadWatch() {
|
|
|
|
buf := make([]byte, 1024)
|
|
|
|
for {
|
|
|
|
_, err := os.Stdin.Read(buf)
|
|
|
|
if err != nil {
|
2023-03-12 23:24:53 +01:00
|
|
|
doShutdown(fmt.Sprintf("stdin closed/error (%v)", err))
|
2023-01-24 00:48:54 +01:00
|
|
|
break
|
2022-10-30 21:05:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-12 23:16:49 +01:00
|
|
|
// ignore SIGHUP
|
|
|
|
func installSignalHandlers() {
|
|
|
|
sigCh := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(sigCh, syscall.SIGHUP)
|
|
|
|
go func() {
|
|
|
|
for sig := range sigCh {
|
2023-03-12 23:24:53 +01:00
|
|
|
doShutdown(fmt.Sprintf("got signal %v", sig))
|
|
|
|
break
|
2023-03-12 23:16:49 +01:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2023-03-12 23:24:53 +01:00
|
|
|
func doShutdown(reason string) {
|
|
|
|
shutdownOnce.Do(func() {
|
2023-11-01 09:26:19 +01:00
|
|
|
log.Printf("[wave] local server %v, start shutdown\n", reason)
|
2023-03-12 23:24:53 +01:00
|
|
|
sendTelemetryWrapper()
|
2023-11-01 09:26:19 +01:00
|
|
|
log.Printf("[wave] closing db connection\n")
|
2023-03-12 23:24:53 +01:00
|
|
|
sstore.CloseDB()
|
2023-11-01 09:26:19 +01:00
|
|
|
log.Printf("[wave] *** shutting down local server\n")
|
2023-03-12 23:24:53 +01:00
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGKILL)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-12 22:39:48 +02:00
|
|
|
func main() {
|
2023-02-24 00:17:47 +01:00
|
|
|
scbase.BuildTime = BuildTime
|
zsh support (#227)
adds zsh support to waveterm. big change, lots going on here. lots of other improvements and bug fixes added while debugging and building out the feature.
Commits:
* refactor shexec parser.go into new package shellenv. separate out bash specific parsing from generic functions
* checkpoint
* work on refactoring shexec. created two new packages shellapi (for bash/zsh specific stuff), and shellutil (shared between shellapi and shexec)
* more refactoring
* create shellapi interface to abstract bash specific functionality
* more refactoring, move bash shell state parsing to shellapi
* move makeRcFile to shellapi. remove all of the 'client' options CLI options from waveshell
* get shellType passed through to server/single paths for waveshell
* add a local shelltype detector
* mock out a zshapi
* move shelltype through more of the code
* get a command to run via zsh
* zsh can now switch directories. poc, needs cleanup
* working on ShellState encoding differences between zsh/bash. Working on parsing zsh decls. move utilfn package into waveshell (shouldn't have been in wavesrv)
* switch to use []byte for vardecl serialization + diffs
* progress on zsh environment. still have issues reconciling init environment with trap environment
* fix typeset argument parsing
* parse promptvars, more zsh specific ignores
* fix bug with promptvar not getting set (wrong check in FeState func)
* add sdk (issue #188) to list of rtnstate commands
* more zsh compatibility -- working with a larger ohmyzsh environment. ignore more variables, handle exit trap better. unique path/fpath. add a processtype variable to base.
* must return a value
* zsh alias parsing/restoring. diff changes (and rtnstate changes). introduces linediff v1.
* force zmodload of zsh/parameter
* starting work on zsh functions
* need a v1 of mapdiff as well (to handle null chars)
* pack/unpack of ints was wrong (one used int and one use uint). turned out we only ever encoded '0' so it worked. that also means it is safe to change unpack to unpackUInt
* reworking for binary encoding of aliases and functions (because of zsh allows any character, including nulls, in names and values)
* fixes, working on functions, issue with line endings
* zsh functions. lots of ugliness here around dealing with line dicipline and cooked stty. new runcommand function to grab output from a non-tty fd. note that we still to run the actual command in a stty to get the proper output.
* write uuid tempdir, cleanup with tmprcfilename code
* hack in some simple zsh function declaration finding code for rtnstate. create function diff for rtnstate that supports zsh
* make sure key order is constant so shell hashes are consistent
* fix problems with state diffs to support new zsh formats. add diff/apply code to shellapi (moved from shellenv), that is now specific to zsh or bash
* add log packet and new shellstate packets
* switch to shellstate map that's also keyed by shelltype
* add shelltype to remoteinstance
* remove shell argument from waveshell
* added new shelltype statemap to remote.go (msh), deal with fallout
* move shellstate out of init packet, and move to an explicit reinit call. try to initialize all of the active shell states
* change dont always store init state (only store on demand). initialize shell states on demand (if not already initialized). allow reset to change shells
* add shellpref field to remote table. use to drive the default shell choice for new tabs
* show shelltag on cmdinput, pass through ri and remote (defaultshellstate)
* bump mshell version to v0.4
* better version validation for shellstate. also relax compatibility requirements for diffing states (shelltype + major version need to match)
* better error handling, check shellstate compatibility during run (on waveshell server)
* add extra separator for bash shellstate processing to deal with spurious output from rc files
* special migration for v30 -- flag invalid bash shell states and show special button in UI to fix
* format
* remove zsh-decls (unused)
* remove test code
* remove debug print
* fix typo
2024-01-17 01:11:04 +01:00
|
|
|
base.ProcessType = base.ProcessType_WaveSrv
|
2023-02-24 00:17:47 +01:00
|
|
|
|
2022-09-06 01:31:22 +02:00
|
|
|
if len(os.Args) >= 2 && os.Args[1] == "--test" {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("running test fn\n")
|
2022-09-06 01:31:22 +02:00
|
|
|
err := test()
|
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] %v\n", err)
|
2022-09-06 01:31:22 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-11-01 09:26:19 +01:00
|
|
|
scHomeDir := scbase.GetWaveHomeDir()
|
|
|
|
log.Printf("[wave] *** starting wavesrv version %s+%s\n", scbase.WaveVersion, scbase.BuildTime)
|
|
|
|
log.Printf("[wave] homedir = %q\n", scHomeDir)
|
2022-10-31 20:40:45 +01:00
|
|
|
|
2023-11-01 09:26:19 +01:00
|
|
|
scLock, err := scbase.AcquireWaveLock()
|
2022-07-08 01:29:14 +02:00
|
|
|
if err != nil || scLock == nil {
|
2023-11-10 01:26:44 +01:00
|
|
|
log.Printf("[error] cannot acquire wave lock (another instance of wavesrv is likely running): %v\n", err)
|
2022-07-08 01:29:14 +02:00
|
|
|
return
|
|
|
|
}
|
2022-07-01 19:48:14 +02:00
|
|
|
if len(os.Args) >= 2 && strings.HasPrefix(os.Args[1], "--migrate") {
|
|
|
|
err := sstore.MigrateCommandOpts(os.Args[1:])
|
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] migrate cmd: %v\n", err)
|
2022-07-01 19:48:14 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2023-11-01 09:26:19 +01:00
|
|
|
authKey, err := scbase.ReadWaveAuthKey()
|
2022-12-21 01:16:46 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("[error] %v\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
GlobalAuthKey = authKey
|
2022-07-08 01:29:14 +02:00
|
|
|
err = sstore.TryMigrateUp()
|
2022-07-01 19:48:14 +02:00
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] migrate up: %v\n", err)
|
2022-07-01 19:48:14 +02:00
|
|
|
return
|
|
|
|
}
|
2022-09-25 09:26:33 +02:00
|
|
|
clientData, err := sstore.EnsureClientData(context.Background())
|
2022-08-27 01:21:19 +02:00
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] ensuring client data: %v\n", err)
|
2022-08-27 01:21:19 +02:00
|
|
|
return
|
|
|
|
}
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("userid = %s\n", clientData.UserId)
|
2022-07-01 21:17:19 +02:00
|
|
|
err = sstore.EnsureLocalRemote(context.Background())
|
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] ensuring local remote: %v\n", err)
|
2022-07-01 21:17:19 +02:00
|
|
|
return
|
|
|
|
}
|
2023-11-02 07:41:04 +01:00
|
|
|
err = sstore.EnsureOneSession(context.Background())
|
2022-07-01 23:45:33 +02:00
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] ensuring default session: %v\n", err)
|
2022-07-01 23:45:33 +02:00
|
|
|
return
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
2022-07-02 02:38:36 +02:00
|
|
|
err = remote.LoadRemotes(context.Background())
|
2022-06-12 22:39:48 +02:00
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[error] loading remotes: %v\n", err)
|
2022-06-12 22:39:48 +02:00
|
|
|
return
|
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
|
2022-07-08 01:29:14 +02:00
|
|
|
err = sstore.HangupAllRunningCmds(context.Background())
|
|
|
|
if err != nil {
|
2023-01-12 05:53:46 +01:00
|
|
|
log.Printf("[error] calling HUP on all running commands: %v\n", err)
|
|
|
|
}
|
|
|
|
err = sstore.ReInitFocus(context.Background())
|
|
|
|
if err != nil {
|
2023-03-15 00:37:22 +01:00
|
|
|
log.Printf("[error] resetting screen focus: %v\n", err)
|
2022-07-08 01:29:14 +02:00
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
|
2023-01-26 07:49:50 +01:00
|
|
|
log.Printf("PCLOUD_ENDPOINT=%s\n", pcloud.GetEndpoint())
|
2024-01-24 02:19:03 +01:00
|
|
|
sstore.UpdateActivityWrap(context.Background(), sstore.ActivityUpdate{NumConns: remote.NumRemotes()}, "numconns") // set at least one record into activity
|
2023-03-12 23:16:49 +01:00
|
|
|
installSignalHandlers()
|
2023-01-26 07:49:50 +01:00
|
|
|
go telemetryLoop()
|
2022-10-30 21:05:53 +01:00
|
|
|
go stdinReadWatch()
|
2022-06-16 03:13:16 +02:00
|
|
|
go runWebSocketServer()
|
2023-03-27 03:48:43 +02:00
|
|
|
go func() {
|
|
|
|
time.Sleep(10 * time.Second)
|
|
|
|
pcloud.StartUpdateWriter()
|
|
|
|
}()
|
2022-06-12 22:39:48 +02:00
|
|
|
gr := mux.NewRouter()
|
2022-12-21 01:16:46 +01:00
|
|
|
gr.HandleFunc("/api/ptyout", AuthKeyWrap(HandleGetPtyOut))
|
|
|
|
gr.HandleFunc("/api/remote-pty", AuthKeyWrap(HandleRemotePty))
|
|
|
|
gr.HandleFunc("/api/rtnstate", AuthKeyWrap(HandleRtnState))
|
2023-03-13 18:50:29 +01:00
|
|
|
gr.HandleFunc("/api/get-screen-lines", AuthKeyWrap(HandleGetScreenLines))
|
2022-12-21 01:16:46 +01:00
|
|
|
gr.HandleFunc("/api/run-command", AuthKeyWrap(HandleRunCommand)).Methods("POST")
|
|
|
|
gr.HandleFunc("/api/get-client-data", AuthKeyWrap(HandleGetClientData))
|
|
|
|
gr.HandleFunc("/api/set-winsize", AuthKeyWrap(HandleSetWinSize))
|
2023-01-17 08:36:52 +01:00
|
|
|
gr.HandleFunc("/api/log-active-state", AuthKeyWrap(HandleLogActiveState))
|
2023-09-01 07:04:31 +02:00
|
|
|
gr.HandleFunc("/api/read-file", AuthKeyWrap(HandleReadFile))
|
|
|
|
gr.HandleFunc("/api/write-file", AuthKeyWrap(HandleWriteFile)).Methods("POST")
|
2022-12-29 02:47:12 +01:00
|
|
|
serverAddr := MainServerAddr
|
|
|
|
if scbase.IsDevMode() {
|
|
|
|
serverAddr = MainServerDevAddr
|
|
|
|
}
|
2022-06-12 22:39:48 +02:00
|
|
|
server := &http.Server{
|
2022-12-29 02:47:12 +01:00
|
|
|
Addr: serverAddr,
|
2022-06-12 22:39:48 +02:00
|
|
|
ReadTimeout: HttpReadTimeout,
|
|
|
|
WriteTimeout: HttpWriteTimeout,
|
|
|
|
MaxHeaderBytes: HttpMaxHeaderBytes,
|
|
|
|
Handler: http.TimeoutHandler(gr, HttpTimeoutDuration, "Timeout"),
|
|
|
|
}
|
|
|
|
server.SetKeepAlivesEnabled(false)
|
2022-12-29 02:47:12 +01:00
|
|
|
log.Printf("Running main server on %s\n", serverAddr)
|
2022-06-12 22:39:48 +02:00
|
|
|
err = server.ListenAndServe()
|
|
|
|
if err != nil {
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("ERROR: %v\n", err)
|
2022-06-12 22:39:48 +02:00
|
|
|
}
|
2023-11-10 01:26:44 +01:00
|
|
|
runtime.KeepAlive(scLock)
|
2022-06-12 22:39:48 +02:00
|
|
|
}
|