implementing a ssh termianl
This commit is contained in:
parent
f6ea852e61
commit
c562a41cb8
18
Makefile
18
Makefile
@ -14,9 +14,14 @@ SRCS=\
|
|||||||
server-ctl.go \
|
server-ctl.go \
|
||||||
server-peer.go \
|
server-peer.go \
|
||||||
server-proxy.go \
|
server-proxy.go \
|
||||||
server-ws.go \
|
|
||||||
system.go
|
system.go
|
||||||
|
|
||||||
|
DATA = \
|
||||||
|
xterm.css \
|
||||||
|
xterm.js \
|
||||||
|
xterm-addon-fit.js \
|
||||||
|
xterm.html
|
||||||
|
|
||||||
CMD_DATA=\
|
CMD_DATA=\
|
||||||
cmd/tls.crt \
|
cmd/tls.crt \
|
||||||
cmd/tls.key
|
cmd/tls.key
|
||||||
@ -27,7 +32,7 @@ CMD_SRCS=\
|
|||||||
|
|
||||||
all: $(NAME)
|
all: $(NAME)
|
||||||
|
|
||||||
$(NAME): $(SRCS) $(CMD_DATA) $(CMD_SRCS)
|
$(NAME): $(DATA) $(SRCS) $(CMD_DATA) $(CMD_SRCS)
|
||||||
CGO_ENABLED=0 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
|
CGO_ENABLED=0 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@ -44,6 +49,15 @@ hodu_grpc.pb.go: hodu.proto
|
|||||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||||
hodu.proto
|
hodu.proto
|
||||||
|
|
||||||
|
xterm.js:
|
||||||
|
curl -L -o "$@" https://cdn.jsdelivr.net/npm/@xterm/xterm/lib/xterm.min.js
|
||||||
|
|
||||||
|
xterm-addon-fit.js:
|
||||||
|
curl -L -o "$@" https://cdn.jsdelivr.net/npm/xterm-addon-fit/lib/xterm-addon-fit.min.js
|
||||||
|
|
||||||
|
xterm.css:
|
||||||
|
curl -L -o "$@" https://cdn.jsdelivr.net/npm/@xterm/xterm/css/xterm.min.css
|
||||||
|
|
||||||
cmd/tls.crt:
|
cmd/tls.crt:
|
||||||
openssl req -x509 -newkey rsa:4096 -keyout cmd/tls.key -out cmd/tls.crt -sha256 -days 36500 -nodes -subj "/CN=$(NAME)" --addext "subjectAltName=DNS:$(NAME),IP:10.0.0.1,IP:::1"
|
openssl req -x509 -newkey rsa:4096 -keyout cmd/tls.key -out cmd/tls.crt -sha256 -days 36500 -nodes -subj "/CN=$(NAME)" --addext "subjectAltName=DNS:$(NAME),IP:10.0.0.1,IP:::1"
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package hodu
|
package hodu
|
||||||
|
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
import "fmt"
|
||||||
import "net/http"
|
import "net/http"
|
||||||
import "net/url"
|
import "net/url"
|
||||||
import "runtime"
|
import "runtime"
|
||||||
@ -154,7 +155,7 @@ func (ctl *client_ctl_client_conns) ServeHTTP(w http.ResponseWriter, req *http.R
|
|||||||
ClientPeerAddr: r.peer_addr,
|
ClientPeerAddr: r.peer_addr,
|
||||||
ServerPeerListenAddr: r.server_peer_listen_addr.String(),
|
ServerPeerListenAddr: r.server_peer_listen_addr.String(),
|
||||||
ServerPeerNet: r.server_peer_net,
|
ServerPeerNet: r.server_peer_net,
|
||||||
ServerPeerOption: r.server_peer_proto.string(),
|
ServerPeerOption: r.server_peer_option.string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
js = append(js, json_out_client_conn{
|
js = append(js, json_out_client_conn{
|
||||||
@ -272,7 +273,7 @@ func (ctl *client_ctl_client_conns_id) ServeHTTP(w http.ResponseWriter, req *htt
|
|||||||
ClientPeerAddr: r.peer_addr,
|
ClientPeerAddr: r.peer_addr,
|
||||||
ServerPeerListenAddr: r.server_peer_listen_addr.String(),
|
ServerPeerListenAddr: r.server_peer_listen_addr.String(),
|
||||||
ServerPeerNet: r.server_peer_net,
|
ServerPeerNet: r.server_peer_net,
|
||||||
ServerPeerOption: r.server_peer_proto.string(),
|
ServerPeerOption: r.server_peer_option.string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
js = &json_out_client_conn{
|
js = &json_out_client_conn{
|
||||||
@ -355,7 +356,7 @@ func (ctl *client_ctl_client_conns_id_routes) ServeHTTP(w http.ResponseWriter, r
|
|||||||
ClientPeerAddr: r.peer_addr,
|
ClientPeerAddr: r.peer_addr,
|
||||||
ServerPeerListenAddr: r.server_peer_listen_addr.String(),
|
ServerPeerListenAddr: r.server_peer_listen_addr.String(),
|
||||||
ServerPeerNet: r.server_peer_net,
|
ServerPeerNet: r.server_peer_net,
|
||||||
ServerPeerOption: r.server_peer_proto.string(),
|
ServerPeerOption: r.server_peer_option.string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
cts.route_mtx.Unlock()
|
cts.route_mtx.Unlock()
|
||||||
@ -366,21 +367,28 @@ func (ctl *client_ctl_client_conns_id_routes) ServeHTTP(w http.ResponseWriter, r
|
|||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
var jcr json_in_client_route
|
var jcr json_in_client_route
|
||||||
var r *ClientRoute
|
var r *ClientRoute
|
||||||
var server_peer_proto RouteOption
|
var server_peer_option RouteOption
|
||||||
|
|
||||||
err = json.NewDecoder(req.Body).Decode(&jcr)
|
err = json.NewDecoder(req.Body).Decode(&jcr)
|
||||||
if err != nil || jcr.ClientPeerAddr == "" {
|
if err != nil {
|
||||||
status_code = http.StatusBadRequest; w.WriteHeader(status_code)
|
status_code = http.StatusBadRequest; w.WriteHeader(status_code)
|
||||||
goto done
|
goto oops
|
||||||
}
|
}
|
||||||
|
|
||||||
server_peer_proto = string_to_route_proto(jcr.ServerPeerOption)
|
if jcr.ClientPeerAddr == "" {
|
||||||
if server_peer_proto == RouteOption(ROUTE_OPTION_UNSPEC) {
|
|
||||||
status_code = http.StatusBadRequest; w.WriteHeader(status_code)
|
status_code = http.StatusBadRequest; w.WriteHeader(status_code)
|
||||||
goto done
|
err = fmt.Errorf("blank client-peer-addr")
|
||||||
|
goto oops;
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err = cts.AddNewClientRoute(jcr.ClientPeerAddr, jcr.ServerPeerServiceAddr, jcr.ServerPeerServiceNet, server_peer_proto)
|
server_peer_option = string_to_route_option(jcr.ServerPeerOption)
|
||||||
|
if server_peer_option == RouteOption(ROUTE_OPTION_UNSPEC) {
|
||||||
|
status_code = http.StatusBadRequest; w.WriteHeader(status_code)
|
||||||
|
err = fmt.Errorf("wrong server-peer-option value - %d", server_peer_option)
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err = cts.AddNewClientRoute(jcr.ClientPeerAddr, jcr.ServerPeerServiceAddr, jcr.ServerPeerServiceNet, server_peer_option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
status_code = http.StatusInternalServerError; w.WriteHeader(status_code)
|
status_code = http.StatusInternalServerError; w.WriteHeader(status_code)
|
||||||
if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops }
|
if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops }
|
||||||
@ -468,7 +476,7 @@ func (ctl *client_ctl_client_conns_id_routes_id) ServeHTTP(w http.ResponseWriter
|
|||||||
ClientPeerAddr: r.peer_addr,
|
ClientPeerAddr: r.peer_addr,
|
||||||
ServerPeerListenAddr: r.server_peer_listen_addr.String(),
|
ServerPeerListenAddr: r.server_peer_listen_addr.String(),
|
||||||
ServerPeerNet: r.server_peer_net,
|
ServerPeerNet: r.server_peer_net,
|
||||||
ServerPeerOption: r.server_peer_proto.string(),
|
ServerPeerOption: r.server_peer_option.string(),
|
||||||
})
|
})
|
||||||
if err != nil { goto oops }
|
if err != nil { goto oops }
|
||||||
|
|
||||||
|
32
client.go
32
client.go
@ -102,12 +102,12 @@ type ClientRoute struct {
|
|||||||
cts *ClientConn
|
cts *ClientConn
|
||||||
id RouteId
|
id RouteId
|
||||||
peer_addr string
|
peer_addr string
|
||||||
peer_proto RouteOption
|
peer_option RouteOption
|
||||||
|
|
||||||
server_peer_listen_addr *net.TCPAddr // actual service-side service address
|
server_peer_listen_addr *net.TCPAddr // actual service-side service address
|
||||||
server_peer_addr string // desired server-side service address
|
server_peer_addr string // desired server-side service address
|
||||||
server_peer_net string
|
server_peer_net string
|
||||||
server_peer_proto RouteOption
|
server_peer_option RouteOption
|
||||||
|
|
||||||
ptc_mtx sync.Mutex
|
ptc_mtx sync.Mutex
|
||||||
ptc_map ClientPeerConnMap
|
ptc_map ClientPeerConnMap
|
||||||
@ -155,7 +155,7 @@ func (g *GuardedPacketStreamClient) Context() context.Context {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
func NewClientRoute(cts *ClientConn, id RouteId, client_peer_addr string, server_peer_svc_addr string, server_peer_svc_net string, server_peer_proto RouteOption) *ClientRoute {
|
func NewClientRoute(cts *ClientConn, id RouteId, client_peer_addr string, server_peer_svc_addr string, server_peer_svc_net string, server_peer_option RouteOption) *ClientRoute {
|
||||||
var r ClientRoute
|
var r ClientRoute
|
||||||
|
|
||||||
r.cts = cts
|
r.cts = cts
|
||||||
@ -164,11 +164,11 @@ func NewClientRoute(cts *ClientConn, id RouteId, client_peer_addr string, server
|
|||||||
r.ptc_cancel_map = make(ClientPeerCancelFuncMap)
|
r.ptc_cancel_map = make(ClientPeerCancelFuncMap)
|
||||||
r.peer_addr = client_peer_addr // client-side peer
|
r.peer_addr = client_peer_addr // client-side peer
|
||||||
// if the client_peer_addr is a domain name, it can't tell between tcp4 and tcp6
|
// if the client_peer_addr is a domain name, it can't tell between tcp4 and tcp6
|
||||||
r.peer_proto = string_to_route_proto(tcp_addr_str_class(client_peer_addr))
|
r.peer_option = string_to_route_option(tcp_addr_str_class(client_peer_addr))
|
||||||
|
|
||||||
r.server_peer_addr = server_peer_svc_addr
|
r.server_peer_addr = server_peer_svc_addr
|
||||||
r.server_peer_net = server_peer_svc_net // permitted network for server-side peer
|
r.server_peer_net = server_peer_svc_net // permitted network for server-side peer
|
||||||
r.server_peer_proto = server_peer_proto
|
r.server_peer_option = server_peer_option
|
||||||
r.stop_req.Store(false)
|
r.stop_req.Store(false)
|
||||||
r.stop_chan = make(chan bool, 8)
|
r.stop_chan = make(chan bool, 8)
|
||||||
|
|
||||||
@ -257,16 +257,16 @@ func (r *ClientRoute) RunTask(wg *sync.WaitGroup) {
|
|||||||
// most useful works are triggered by ReportEvent() and done by ConnectToPeer()
|
// most useful works are triggered by ReportEvent() and done by ConnectToPeer()
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
err = r.cts.psc.Send(MakeRouteStartPacket(r.id, r.server_peer_proto, r.peer_addr, r.server_peer_addr, r.server_peer_net))
|
err = r.cts.psc.Send(MakeRouteStartPacket(r.id, r.server_peer_option, r.peer_addr, r.server_peer_addr, r.server_peer_net))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG,
|
r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG,
|
||||||
"Failed to send route_start for route(%d,%s,%v,%v) to %s",
|
"Failed to send route_start for route(%d,%s,%v,%v) to %s",
|
||||||
r.id, r.peer_addr, r.server_peer_proto, r.server_peer_net, r.cts.remote_addr)
|
r.id, r.peer_addr, r.server_peer_option, r.server_peer_net, r.cts.remote_addr)
|
||||||
goto done
|
goto done
|
||||||
} else {
|
} else {
|
||||||
r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG,
|
r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG,
|
||||||
"Sent route_start for route(%d,%s,%v,%v) to %s",
|
"Sent route_start for route(%d,%s,%v,%v) to %s",
|
||||||
r.id, r.peer_addr, r.server_peer_proto, r.server_peer_net, r.cts.remote_addr)
|
r.id, r.peer_addr, r.server_peer_option, r.server_peer_net, r.cts.remote_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
main_loop:
|
main_loop:
|
||||||
@ -281,15 +281,15 @@ done:
|
|||||||
r.ReqStop()
|
r.ReqStop()
|
||||||
r.ptc_wg.Wait() // wait for all peer tasks are finished
|
r.ptc_wg.Wait() // wait for all peer tasks are finished
|
||||||
|
|
||||||
err = r.cts.psc.Send(MakeRouteStopPacket(r.id, r.server_peer_proto, r.peer_addr, r.server_peer_addr, r.server_peer_net))
|
err = r.cts.psc.Send(MakeRouteStopPacket(r.id, r.server_peer_option, r.peer_addr, r.server_peer_addr, r.server_peer_net))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG,
|
r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG,
|
||||||
"Failed to route_stop for route(%d,%s,%v,%v) to %s - %s",
|
"Failed to route_stop for route(%d,%s,%v,%v) to %s - %s",
|
||||||
r.id, r.peer_addr, r.server_peer_proto, r.server_peer_net, r.cts.remote_addr, err.Error())
|
r.id, r.peer_addr, r.server_peer_option, r.server_peer_net, r.cts.remote_addr, err.Error())
|
||||||
} else {
|
} else {
|
||||||
r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG,
|
r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG,
|
||||||
"Sent route_stop for route(%d,%s,%v,%v) to %s",
|
"Sent route_stop for route(%d,%s,%v,%v) to %s",
|
||||||
r.id, r.peer_addr, r.server_peer_proto, r.server_peer_net, r.cts.remote_addr)
|
r.id, r.peer_addr, r.server_peer_option, r.server_peer_net, r.cts.remote_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.cts.RemoveClientRoute(r)
|
r.cts.RemoveClientRoute(r)
|
||||||
@ -305,7 +305,7 @@ func (r *ClientRoute) ReqStop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClientRoute) ConnectToPeer(pts_id PeerId, route_proto RouteOption, pts_raddr string, pts_laddr string, wg *sync.WaitGroup) {
|
func (r *ClientRoute) ConnectToPeer(pts_id PeerId, route_option RouteOption, pts_raddr string, pts_laddr string, wg *sync.WaitGroup) {
|
||||||
var err error
|
var err error
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
var real_conn *net.TCPConn
|
var real_conn *net.TCPConn
|
||||||
@ -319,7 +319,7 @@ func (r *ClientRoute) ConnectToPeer(pts_id PeerId, route_proto RouteOption, pts_
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
// TODO: handle TTY
|
// TODO: handle TTY
|
||||||
// if route_proto & RouteOption(ROUTE_OPTION_TTY) it must create a pseudo-tty insteaad of connecting to tcp address
|
// if route_option & RouteOption(ROUTE_OPTION_TTY) it must create a pseudo-tty insteaad of connecting to tcp address
|
||||||
//
|
//
|
||||||
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@ -470,7 +470,7 @@ func (r *ClientRoute) ReportEvent(pts_id PeerId, event_type PACKET_KIND, event_d
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
r.ptc_wg.Add(1)
|
r.ptc_wg.Add(1)
|
||||||
go r.ConnectToPeer(pts_id, r.peer_proto, pd.RemoteAddrStr, pd.LocalAddrStr, &r.ptc_wg)
|
go r.ConnectToPeer(pts_id, r.peer_option, pd.RemoteAddrStr, pd.LocalAddrStr, &r.ptc_wg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -735,8 +735,10 @@ func (cts *ClientConn) AddClientRoutes(peer_addrs []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
option = RouteOption(ROUTE_OPTION_TCP)
|
option = RouteOption(ROUTE_OPTION_TCP)
|
||||||
// automatic determination of optioncol for common ports
|
// automatic determination of protocol for common ports
|
||||||
switch port {
|
switch port {
|
||||||
|
case "22":
|
||||||
|
option |= RouteOption(ROUTE_OPTION_SSH)
|
||||||
case "80":
|
case "80":
|
||||||
option |= RouteOption(ROUTE_OPTION_HTTP)
|
option |= RouteOption(ROUTE_OPTION_HTTP)
|
||||||
case "443":
|
case "443":
|
||||||
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module hodu
|
|||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
golang.org/x/crypto v0.26.0
|
||||||
golang.org/x/net v0.28.0
|
golang.org/x/net v0.28.0
|
||||||
golang.org/x/sys v0.24.0
|
golang.org/x/sys v0.24.0
|
||||||
google.golang.org/grpc v1.67.1
|
google.golang.org/grpc v1.67.1
|
||||||
|
4
go.sum
4
go.sum
@ -1,9 +1,13 @@
|
|||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||||
|
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||||
|
32
hodu.go
32
hodu.go
@ -54,7 +54,7 @@ func tcp_addr_str_class(addr string) string {
|
|||||||
return "tcp"
|
return "tcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
func word_to_route_proto(word string) RouteOption {
|
func word_to_route_option(word string) RouteOption {
|
||||||
switch word {
|
switch word {
|
||||||
case "tcp4":
|
case "tcp4":
|
||||||
return RouteOption(ROUTE_OPTION_TCP4)
|
return RouteOption(ROUTE_OPTION_TCP4)
|
||||||
@ -68,35 +68,37 @@ func word_to_route_proto(word string) RouteOption {
|
|||||||
return RouteOption(ROUTE_OPTION_HTTP)
|
return RouteOption(ROUTE_OPTION_HTTP)
|
||||||
case "https":
|
case "https":
|
||||||
return RouteOption(ROUTE_OPTION_HTTPS)
|
return RouteOption(ROUTE_OPTION_HTTPS)
|
||||||
|
case "ssh":
|
||||||
|
return RouteOption(ROUTE_OPTION_SSH)
|
||||||
}
|
}
|
||||||
|
|
||||||
return RouteOption(ROUTE_OPTION_UNSPEC)
|
return RouteOption(ROUTE_OPTION_UNSPEC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func string_to_route_proto(desc string) RouteOption {
|
func string_to_route_option(desc string) RouteOption {
|
||||||
var fld string
|
var fld string
|
||||||
var proto RouteOption
|
var option RouteOption
|
||||||
var p RouteOption
|
var p RouteOption
|
||||||
|
|
||||||
proto = RouteOption(0)
|
option = RouteOption(0)
|
||||||
for _, fld = range strings.Fields(desc) {
|
for _, fld = range strings.Fields(desc) {
|
||||||
p = word_to_route_proto(fld)
|
p = word_to_route_option(fld)
|
||||||
if p == RouteOption(ROUTE_OPTION_UNSPEC) { return p }
|
if p == RouteOption(ROUTE_OPTION_UNSPEC) { return p }
|
||||||
proto |= p
|
option |= p
|
||||||
}
|
}
|
||||||
return proto
|
return option
|
||||||
}
|
}
|
||||||
|
|
||||||
func (proto RouteOption) string() string {
|
func (option RouteOption) string() string {
|
||||||
var str string
|
var str string
|
||||||
|
|
||||||
str = ""
|
str = ""
|
||||||
if proto & RouteOption(ROUTE_OPTION_TCP6) != 0 { str += " tcp6" }
|
if option & RouteOption(ROUTE_OPTION_TCP6) != 0 { str += " tcp6" }
|
||||||
if proto & RouteOption(ROUTE_OPTION_TCP4) != 0 { str += " tcp4" }
|
if option & RouteOption(ROUTE_OPTION_TCP4) != 0 { str += " tcp4" }
|
||||||
if proto & RouteOption(ROUTE_OPTION_TCP) != 0 { str += " tcp" }
|
if option & RouteOption(ROUTE_OPTION_TCP) != 0 { str += " tcp" }
|
||||||
if proto & RouteOption(ROUTE_OPTION_TTY) != 0 { str += " tty" }
|
if option & RouteOption(ROUTE_OPTION_TTY) != 0 { str += " tty" }
|
||||||
if proto & RouteOption(ROUTE_OPTION_HTTP) != 0 { str += " http" }
|
if option & RouteOption(ROUTE_OPTION_HTTP) != 0 { str += " http" }
|
||||||
if proto & RouteOption(ROUTE_OPTION_HTTPS) != 0 { str += " https" }
|
if option & RouteOption(ROUTE_OPTION_HTTPS) != 0 { str += " https" }
|
||||||
|
if option & RouteOption(ROUTE_OPTION_SSH) != 0 { str += " ssh" }
|
||||||
if str == "" { return str }
|
if str == "" { return str }
|
||||||
return str[1:] // remove the leading space
|
return str[1:] // remove the leading space
|
||||||
}
|
}
|
||||||
|
41
hodu.pb.go
41
hodu.pb.go
@ -30,6 +30,7 @@ const (
|
|||||||
ROUTE_OPTION_TTY ROUTE_OPTION = 8
|
ROUTE_OPTION_TTY ROUTE_OPTION = 8
|
||||||
ROUTE_OPTION_HTTP ROUTE_OPTION = 16
|
ROUTE_OPTION_HTTP ROUTE_OPTION = 16
|
||||||
ROUTE_OPTION_HTTPS ROUTE_OPTION = 32
|
ROUTE_OPTION_HTTPS ROUTE_OPTION = 32
|
||||||
|
ROUTE_OPTION_SSH ROUTE_OPTION = 64
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for ROUTE_OPTION.
|
// Enum value maps for ROUTE_OPTION.
|
||||||
@ -42,6 +43,7 @@ var (
|
|||||||
8: "TTY",
|
8: "TTY",
|
||||||
16: "HTTP",
|
16: "HTTP",
|
||||||
32: "HTTPS",
|
32: "HTTPS",
|
||||||
|
64: "SSH",
|
||||||
}
|
}
|
||||||
ROUTE_OPTION_value = map[string]int32{
|
ROUTE_OPTION_value = map[string]int32{
|
||||||
"UNSPEC": 0,
|
"UNSPEC": 0,
|
||||||
@ -51,6 +53,7 @@ var (
|
|||||||
"TTY": 8,
|
"TTY": 8,
|
||||||
"HTTP": 16,
|
"HTTP": 16,
|
||||||
"HTTPS": 32,
|
"HTTPS": 32,
|
||||||
|
"SSH": 64,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -566,30 +569,30 @@ var file_hodu_proto_rawDesc = []byte{
|
|||||||
0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x65, 0x73, 0x63, 0x48, 0x00, 0x52, 0x04, 0x50,
|
0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x65, 0x73, 0x63, 0x48, 0x00, 0x52, 0x04, 0x50,
|
||||||
0x65, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28,
|
0x65, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
0x0b, 0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x04,
|
0x0b, 0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x04,
|
||||||
0x44, 0x61, 0x74, 0x61, 0x42, 0x03, 0x0a, 0x01, 0x55, 0x2a, 0x55, 0x0a, 0x0c, 0x52, 0x4f, 0x55,
|
0x44, 0x61, 0x74, 0x61, 0x42, 0x03, 0x0a, 0x01, 0x55, 0x2a, 0x5e, 0x0a, 0x0c, 0x52, 0x4f, 0x55,
|
||||||
0x54, 0x45, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x4e, 0x53,
|
0x54, 0x45, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x4e, 0x53,
|
||||||
0x50, 0x45, 0x43, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08,
|
0x50, 0x45, 0x43, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08,
|
||||||
0x0a, 0x04, 0x54, 0x43, 0x50, 0x34, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x43, 0x50, 0x36,
|
0x0a, 0x04, 0x54, 0x43, 0x50, 0x34, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x43, 0x50, 0x36,
|
||||||
0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x54, 0x59, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x48,
|
0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x54, 0x59, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x48,
|
||||||
0x54, 0x54, 0x50, 0x10, 0x10, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x20,
|
0x54, 0x54, 0x50, 0x10, 0x10, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x20,
|
||||||
0x2a, 0xb5, 0x01, 0x0a, 0x0b, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44,
|
0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 0x48, 0x10, 0x40, 0x2a, 0xb5, 0x01, 0x0a, 0x0b, 0x50, 0x41,
|
||||||
0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f,
|
0x43, 0x4b, 0x45, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53,
|
||||||
0x0a, 0x0b, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x01, 0x12,
|
0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x4f, 0x55, 0x54, 0x45,
|
||||||
0x0e, 0x0a, 0x0a, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x02, 0x12,
|
0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x4f, 0x55, 0x54,
|
||||||
0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44,
|
0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54,
|
||||||
0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50,
|
0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x52,
|
||||||
0x50, 0x45, 0x44, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x53, 0x54,
|
0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x04, 0x12, 0x10,
|
||||||
0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f,
|
0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x05,
|
||||||
0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45,
|
0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44,
|
||||||
0x52, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x50,
|
0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54,
|
||||||
0x45, 0x45, 0x52, 0x5f, 0x45, 0x4f, 0x46, 0x10, 0x08, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x45, 0x45,
|
0x45, 0x44, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x45, 0x4f, 0x46,
|
||||||
0x52, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x09, 0x32, 0x49, 0x0a, 0x04, 0x48, 0x6f, 0x64, 0x75,
|
0x10, 0x08, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10,
|
||||||
0x12, 0x19, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x53, 0x65, 0x65, 0x64, 0x12, 0x05, 0x2e, 0x53, 0x65,
|
0x09, 0x32, 0x49, 0x0a, 0x04, 0x48, 0x6f, 0x64, 0x75, 0x12, 0x19, 0x0a, 0x07, 0x47, 0x65, 0x74,
|
||||||
0x65, 0x64, 0x1a, 0x05, 0x2e, 0x53, 0x65, 0x65, 0x64, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x0c, 0x50,
|
0x53, 0x65, 0x65, 0x64, 0x12, 0x05, 0x2e, 0x53, 0x65, 0x65, 0x64, 0x1a, 0x05, 0x2e, 0x53, 0x65,
|
||||||
0x61, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x07, 0x2e, 0x50, 0x61,
|
0x65, 0x64, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x0c, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74,
|
||||||
0x63, 0x6b, 0x65, 0x74, 0x1a, 0x07, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x00, 0x28,
|
0x72, 0x65, 0x61, 0x6d, 0x12, 0x07, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x1a, 0x07, 0x2e,
|
||||||
0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x68, 0x6f, 0x64, 0x75, 0x62, 0x06, 0x70,
|
0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x2e, 0x2f, 0x68, 0x6f, 0x64, 0x75, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -26,6 +26,7 @@ enum ROUTE_OPTION {
|
|||||||
TTY = 8;
|
TTY = 8;
|
||||||
HTTP = 16;
|
HTTP = 16;
|
||||||
HTTPS = 32;
|
HTTPS = 32;
|
||||||
|
SSH = 64;
|
||||||
};
|
};
|
||||||
|
|
||||||
message RouteDesc {
|
message RouteDesc {
|
||||||
|
43
server-ws.go
43
server-ws.go
@ -1,43 +0,0 @@
|
|||||||
package hodu
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
import "net/http"
|
|
||||||
import "golang.org/x/net/websocket"
|
|
||||||
|
|
||||||
type server_ctl_ws_tty struct {
|
|
||||||
s *Server
|
|
||||||
h websocket.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func server_ws_tty (ws* websocket.Conn) {
|
|
||||||
var msg []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ws.Write([]byte("hello world\r\n"))
|
|
||||||
ws.Write([]byte("it's so wrong. it's awesome\r\n"))
|
|
||||||
ws.Write([]byte("it's so wrong. 동키가 지나간다.it's awesome\r\n"))
|
|
||||||
|
|
||||||
for {
|
|
||||||
err = websocket.Message.Receive(ws, &msg)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
} else if len(msg) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf ("RECEIVED MESSAGE [%v]\n", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func new_server_ctl_ws_tty(s *Server) *server_ctl_ws_tty {
|
|
||||||
return &server_ctl_ws_tty{s: s, h: websocket.Handler(server_ws_tty)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctl *server_ctl_ws_tty) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
ctl.h.ServeHTTP(w, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
21
server.go
21
server.go
@ -6,14 +6,13 @@ import "errors"
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
import "io"
|
import "io"
|
||||||
import "log"
|
import "log"
|
||||||
//import "math/rand"
|
|
||||||
import "net"
|
import "net"
|
||||||
import "net/http"
|
import "net/http"
|
||||||
import "net/netip"
|
import "net/netip"
|
||||||
import "os"
|
|
||||||
import "sync"
|
import "sync"
|
||||||
import "sync/atomic"
|
import "sync/atomic"
|
||||||
|
|
||||||
|
import "golang.org/x/net/websocket"
|
||||||
import "google.golang.org/grpc"
|
import "google.golang.org/grpc"
|
||||||
import "google.golang.org/grpc/credentials"
|
import "google.golang.org/grpc/credentials"
|
||||||
//import "google.golang.org/grpc/metadata"
|
//import "google.golang.org/grpc/metadata"
|
||||||
@ -874,7 +873,6 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs
|
|||||||
var addr string
|
var addr string
|
||||||
var gl *net.TCPListener
|
var gl *net.TCPListener
|
||||||
var i int
|
var i int
|
||||||
var cwd string
|
|
||||||
var hs_log *log.Logger
|
var hs_log *log.Logger
|
||||||
var opts []grpc.ServerOption
|
var opts []grpc.ServerOption
|
||||||
|
|
||||||
@ -934,10 +932,7 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs
|
|||||||
|
|
||||||
s.ctl_prefix = ctl_prefix
|
s.ctl_prefix = ctl_prefix
|
||||||
s.ctl_mux = http.NewServeMux()
|
s.ctl_mux = http.NewServeMux()
|
||||||
cwd, _ = os.Getwd() // TODO:
|
|
||||||
|
|
||||||
s.ctl_mux.Handle(s.ctl_prefix + "/ui/", http.StripPrefix(s.ctl_prefix, http.FileServer(http.Dir(cwd)))) // TODO: proper directory. it must not use the current working directory...
|
|
||||||
s.ctl_mux.Handle(s.ctl_prefix + "/ws/tty", new_server_ctl_ws_tty(&s))
|
|
||||||
s.ctl_mux.Handle(s.ctl_prefix + "/server-conns", &server_ctl_server_conns{s: &s})
|
s.ctl_mux.Handle(s.ctl_prefix + "/server-conns", &server_ctl_server_conns{s: &s})
|
||||||
s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}", &server_ctl_server_conns_id{s: &s})
|
s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}", &server_ctl_server_conns_id{s: &s})
|
||||||
s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}/routes", &server_ctl_server_conns_id_routes{s: &s})
|
s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}/routes", &server_ctl_server_conns_id_routes{s: &s})
|
||||||
@ -959,7 +954,21 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
s.pxy_mux = http.NewServeMux() // TODO: make /_init configurable...
|
s.pxy_mux = http.NewServeMux() // TODO: make /_init configurable...
|
||||||
|
s.pxy_mux.Handle("/_ssh-ws/{conn_id}/{route_id}",
|
||||||
|
websocket.Handler(func(ws *websocket.Conn) {
|
||||||
|
server_proxy_serve_ssh_ws(ws, &s)
|
||||||
|
}))
|
||||||
|
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/", &server_proxy_xterm_file{s: &s, file: "xterm.html"})
|
||||||
|
s.pxy_mux.Handle("/_ssh/xterm.js", &server_proxy_xterm_file{s: &s, file: "xterm.js"})
|
||||||
|
s.pxy_mux.Handle("/_ssh/xterm-addon-fit.js", &server_proxy_xterm_file{s: &s, file: "xterm-addon-fit.js"})
|
||||||
|
s.pxy_mux.Handle("/_ssh/xterm.css", &server_proxy_xterm_file{s: &s, file: "xterm.css"})
|
||||||
|
s.pxy_mux.Handle("/_ssh/", &server_proxy_xterm_file{s: &s, file: "_forbidden"})
|
||||||
|
|
||||||
|
//cwd, _ = os.Getwd() // TODO:
|
||||||
|
//s.pxy_mux.Handle(s.ctl_prefix + "/ui/", http.StripPrefix(s.ctl_prefix, http.FileServer(http.Dir(cwd)))) // TODO: proper directory. it must not use the current working directory...
|
||||||
|
|
||||||
s.pxy_mux.Handle("/_init/{conn_id}/{route_id}/{trailer...}", &server_proxy_http_init{s: &s})
|
s.pxy_mux.Handle("/_init/{conn_id}/{route_id}/{trailer...}", &server_proxy_http_init{s: &s})
|
||||||
s.pxy_mux.Handle("/", &server_proxy_http_main{s: &s})
|
s.pxy_mux.Handle("/", &server_proxy_http_main{s: &s})
|
||||||
|
|
||||||
|
8
xterm-addon-fit.js
Normal file
8
xterm-addon-fit.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Skipped minification because the original files appears to be already minified.
|
||||||
|
* Original file: /npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js
|
||||||
|
*
|
||||||
|
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||||
|
*/
|
||||||
|
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})()));
|
||||||
|
//# sourceMappingURL=xterm-addon-fit.js.map
|
8
xterm.css
Normal file
8
xterm.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Minified by jsDelivr using clean-css v5.3.2.
|
||||||
|
* Original file: /npm/@xterm/xterm@5.5.0/css/xterm.css
|
||||||
|
*
|
||||||
|
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||||
|
*/
|
||||||
|
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}
|
||||||
|
/*# sourceMappingURL=/sm/97377c0c258e109358121823f5790146c714989366481f90e554c42277efb500.map */
|
76
xterm.html
Normal file
76
xterm.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<title>Terminal</title>
|
||||||
|
<link rel="stylesheet" href="/_ssh/xterm.css" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#terminal-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="/_ssh/xterm.js"></script>
|
||||||
|
<script src="/_ssh/xterm-addon-fit.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.addEventListener("load", function(event) {
|
||||||
|
const term = new window.Terminal();
|
||||||
|
const fit = new window.FitAddon.FitAddon();
|
||||||
|
const textDecoder = new TextDecoder();
|
||||||
|
|
||||||
|
let currentLocation = window.location.host;
|
||||||
|
let prefix = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||||
|
const socket = new WebSocket(prefix + currentLocation + '/_ssh-ws/{{ .ConnId }}/{{ .RouteId }}');
|
||||||
|
socket.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
|
term.loadAddon(fit)
|
||||||
|
term.open(document.getElementById('terminal-container'));
|
||||||
|
term.writeln('Connecting...');
|
||||||
|
|
||||||
|
const fit_term = function() {
|
||||||
|
fit.fit();
|
||||||
|
socket.send(JSON.stringify({ type: 'resize', data: term.rows.toString() + " " + term.cols.toString() }));
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onopen = function () {
|
||||||
|
term.writeln('Connected');
|
||||||
|
fit_term();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = function(event) {
|
||||||
|
if (typeof event.data === 'string') {
|
||||||
|
term.write(event.data);
|
||||||
|
} else {
|
||||||
|
const text = textDecoder.decode(new Uint8Array(event.data));
|
||||||
|
term.write(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = function (event) {
|
||||||
|
console.error('WebSocket error:', event);
|
||||||
|
term.writeln('WebSocket error. See console for details.');
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = function () {
|
||||||
|
term.writeln('Disconnected');
|
||||||
|
};
|
||||||
|
|
||||||
|
term.onData(function(data) {
|
||||||
|
socket.send(JSON.stringify({ type: 'key', data: data }));
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', fit_term);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="terminal-container"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user