WIP: gRPC integration, initial implementation
This commit is contained in:
13
go.mod
13
go.mod
@@ -1,14 +1,19 @@
|
|||||||
module tunnel_pls
|
module tunnel_pls
|
||||||
|
|
||||||
go 1.24.4
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
git.fossy.my.id/bagas/tunnel-please-grpc v1.0.0
|
||||||
github.com/caddyserver/certmagic v0.25.0
|
github.com/caddyserver/certmagic v0.25.0
|
||||||
github.com/charmbracelet/bubbles v0.21.0
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
|
github.com/golang/protobuf v1.5.4
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/libdns/cloudflare v0.2.2
|
github.com/libdns/cloudflare v0.2.2
|
||||||
|
github.com/muesli/termenv v0.16.0
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.46.0
|
||||||
|
google.golang.org/grpc v1.78.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -16,7 +21,6 @@ require (
|
|||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
@@ -31,7 +35,6 @@ require (
|
|||||||
github.com/miekg/dns v1.1.68 // indirect
|
github.com/miekg/dns v1.1.68 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
github.com/sahilm/fuzzy v0.1.1 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
@@ -45,4 +48,8 @@ require (
|
|||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/tools v0.39.0 // indirect
|
golang.org/x/tools v0.39.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace git.fossy.my.id/bagas/tunnel-please-grpc => ../tunnel-please-grpc
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -28,8 +28,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
@@ -75,6 +83,18 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
|||||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||||
|
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
@@ -103,5 +123,13 @@ golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
|||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package grpc
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -6,15 +6,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
"tunnel_pls/session"
|
||||||
|
|
||||||
"git.fossy.my.id/bagas/tunnel-please-grpc/gen"
|
"git.fossy.my.id/bagas/tunnel-please-grpc/gen"
|
||||||
|
"github.com/golang/protobuf/ptypes/empty"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/health/grpc_health_v1"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientConfig struct {
|
type GrpcConfig struct {
|
||||||
Address string
|
Address string
|
||||||
UseTLS bool
|
UseTLS bool
|
||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool
|
||||||
@@ -25,12 +30,14 @@ type ClientConfig struct {
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
conn *grpc.ClientConn
|
conn *grpc.ClientConn
|
||||||
config *ClientConfig
|
config *GrpcConfig
|
||||||
|
sessionRegistry session.Registry
|
||||||
IdentityService gen.IdentityClient
|
IdentityService gen.IdentityClient
|
||||||
|
eventService gen.EventServiceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() *ClientConfig {
|
func DefaultConfig() *GrpcConfig {
|
||||||
return &ClientConfig{
|
return &GrpcConfig{
|
||||||
Address: "localhost:50051",
|
Address: "localhost:50051",
|
||||||
UseTLS: false,
|
UseTLS: false,
|
||||||
InsecureSkipVerify: false,
|
InsecureSkipVerify: false,
|
||||||
@@ -40,7 +47,7 @@ func DefaultConfig() *ClientConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(config *ClientConfig) (*Client, error) {
|
func New(config *GrpcConfig, sessionRegistry session.Registry) (*Client, error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = DefaultConfig()
|
config = DefaultConfig()
|
||||||
}
|
}
|
||||||
@@ -61,15 +68,15 @@ func NewClient(config *ClientConfig) (*Client, error) {
|
|||||||
kaParams := keepalive.ClientParameters{
|
kaParams := keepalive.ClientParameters{
|
||||||
Time: 10 * time.Second,
|
Time: 10 * time.Second,
|
||||||
Timeout: 3 * time.Second,
|
Timeout: 3 * time.Second,
|
||||||
PermitWithoutStream: true,
|
PermitWithoutStream: false,
|
||||||
}
|
}
|
||||||
opts = append(opts, grpc.WithKeepaliveParams(kaParams))
|
opts = append(opts, grpc.WithKeepaliveParams(kaParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts,
|
opts = append(opts,
|
||||||
grpc.WithDefaultCallOptions(
|
grpc.WithDefaultCallOptions(
|
||||||
grpc.MaxCallRecvMsgSize(4*1024*1024), // 4MB
|
grpc.MaxCallRecvMsgSize(4*1024*1024),
|
||||||
grpc.MaxCallSendMsgSize(4*1024*1024), // 4MB
|
grpc.MaxCallSendMsgSize(4*1024*1024),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,15 +85,98 @@ func NewClient(config *ClientConfig) (*Client, error) {
|
|||||||
return nil, fmt.Errorf("failed to connect to gRPC server at %s: %w", config.Address, err)
|
return nil, fmt.Errorf("failed to connect to gRPC server at %s: %w", config.Address, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Successfully connected to gRPC server at %s", config.Address)
|
|
||||||
identityService := gen.NewIdentityClient(conn)
|
identityService := gen.NewIdentityClient(conn)
|
||||||
|
eventService := gen.NewEventServiceClient(conn)
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
config: config,
|
config: config,
|
||||||
IdentityService: identityService,
|
IdentityService: identityService,
|
||||||
|
eventService: eventService,
|
||||||
|
sessionRegistry: sessionRegistry,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) SubscribeEvents(ctx context.Context) error {
|
||||||
|
for {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
log.Println("Context cancelled, stopping event subscription")
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Subscribing to events...")
|
||||||
|
stream, err := c.eventService.Subscribe(ctx, &empty.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to subscribe: %v. Retrying in 10 seconds...", err)
|
||||||
|
select {
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.processEventStream(ctx, stream); err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
log.Printf("Stream error: %v. Reconnecting in 10 seconds...", err)
|
||||||
|
select {
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) processEventStream(ctx context.Context, stream gen.EventService_SubscribeClient) error {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
event, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
st, ok := status.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("non-gRPC error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch st.Code() {
|
||||||
|
case codes.Unavailable, codes.Canceled, codes.DeadlineExceeded:
|
||||||
|
return fmt.Errorf("stream closed [%s]: %s", st.Code(), st.Message())
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("gRPC error [%s]: %s", st.Code(), st.Message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event != nil {
|
||||||
|
dataEvent := event.GetDataEvent()
|
||||||
|
if dataEvent != nil {
|
||||||
|
oldSlug := dataEvent.GetOld()
|
||||||
|
newSlug := dataEvent.GetNew()
|
||||||
|
|
||||||
|
userSession, exist := c.sessionRegistry.Get(oldSlug)
|
||||||
|
if !exist {
|
||||||
|
log.Printf("Session with slug '%s' not found, ignoring event", oldSlug)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
success := c.sessionRegistry.Update(oldSlug, newSlug)
|
||||||
|
|
||||||
|
if success {
|
||||||
|
log.Printf("Successfully updated session slug from '%s' to '%s'", oldSlug, newSlug)
|
||||||
|
userSession.GetInteraction().Redraw()
|
||||||
|
} else {
|
||||||
|
log.Printf("Failed to update session slug from '%s' to '%s'", oldSlug, newSlug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetConnection() *grpc.ClientConn {
|
func (c *Client) GetConnection() *grpc.ClientConn {
|
||||||
return c.conn
|
return c.conn
|
||||||
}
|
}
|
||||||
@@ -99,88 +189,23 @@ func (c *Client) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) IsConnected() bool {
|
func (c *Client) CheckServerHealth(ctx context.Context) error {
|
||||||
if c.conn == nil {
|
healthClient := grpc_health_v1.NewHealthClient(c.GetConnection())
|
||||||
return false
|
resp, err := healthClient.Check(ctx, &grpc_health_v1.HealthCheckRequest{
|
||||||
}
|
Service: "",
|
||||||
state := c.conn.GetState()
|
})
|
||||||
return state.String() == "READY" || state.String() == "IDLE"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Reconnect() error {
|
|
||||||
if err := c.Close(); err != nil {
|
|
||||||
log.Printf("Warning: error closing existing connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts []grpc.DialOption
|
|
||||||
|
|
||||||
if c.config.UseTLS {
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
InsecureSkipVerify: c.config.InsecureSkipVerify,
|
|
||||||
}
|
|
||||||
creds := credentials.NewTLS(tlsConfig)
|
|
||||||
opts = append(opts, grpc.WithTransportCredentials(creds))
|
|
||||||
} else {
|
|
||||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.config.KeepAlive {
|
|
||||||
kaParams := keepalive.ClientParameters{
|
|
||||||
Time: 10 * time.Second,
|
|
||||||
Timeout: 3 * time.Second,
|
|
||||||
PermitWithoutStream: true,
|
|
||||||
}
|
|
||||||
opts = append(opts, grpc.WithKeepaliveParams(kaParams))
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = append(opts,
|
|
||||||
grpc.WithDefaultCallOptions(
|
|
||||||
grpc.MaxCallRecvMsgSize(4*1024*1024),
|
|
||||||
grpc.MaxCallSendMsgSize(4*1024*1024),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
conn, err := grpc.NewClient(c.config.Address, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to reconnect to gRPC server at %s: %w", c.config.Address, err)
|
return fmt.Errorf("health check failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn = conn
|
if resp.Status != grpc_health_v1.HealthCheckResponse_SERVING {
|
||||||
log.Printf("Successfully reconnected to gRPC server at %s", c.config.Address)
|
return fmt.Errorf("server not serving: %v", resp.Status)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) WaitForReady(ctx context.Context) error {
|
|
||||||
if c.conn == nil {
|
|
||||||
return fmt.Errorf("connection is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok := ctx.Deadline()
|
|
||||||
if !ok {
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
ctx, cancel = context.WithTimeout(ctx, c.config.Timeout)
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState := c.conn.GetState()
|
|
||||||
for currentState.String() != "READY" {
|
|
||||||
if !c.conn.WaitForStateChange(ctx, currentState) {
|
|
||||||
return fmt.Errorf("timeout waiting for connection to be ready")
|
|
||||||
}
|
|
||||||
currentState = c.conn.GetState()
|
|
||||||
|
|
||||||
if currentState.String() == "READY" || currentState.String() == "IDLE" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentState.String() == "SHUTDOWN" || currentState.String() == "TRANSIENT_FAILURE" {
|
|
||||||
return fmt.Errorf("connection is in %s state", currentState.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetConfig() *ClientConfig {
|
func (c *Client) GetConfig() *GrpcConfig {
|
||||||
return c.config
|
return c.config
|
||||||
}
|
}
|
||||||
|
|||||||
45
main.go
45
main.go
@@ -1,12 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
"tunnel_pls/internal/config"
|
"tunnel_pls/internal/config"
|
||||||
|
"tunnel_pls/internal/grpc/client"
|
||||||
"tunnel_pls/internal/key"
|
"tunnel_pls/internal/key"
|
||||||
"tunnel_pls/server"
|
"tunnel_pls/server"
|
||||||
"tunnel_pls/session"
|
"tunnel_pls/session"
|
||||||
@@ -61,9 +64,49 @@ func main() {
|
|||||||
sshConfig.AddHostKey(private)
|
sshConfig.AddHostKey(private)
|
||||||
sessionRegistry := session.NewRegistry()
|
sessionRegistry := session.NewRegistry()
|
||||||
|
|
||||||
app, err := server.NewServer(sshConfig, sessionRegistry)
|
grpcClient, err := client.New(&client.GrpcConfig{
|
||||||
|
Address: "localhost:8080",
|
||||||
|
UseTLS: false,
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
KeepAlive: true,
|
||||||
|
MaxRetries: 3,
|
||||||
|
}, sessionRegistry)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func(grpcClient *client.Client) {
|
||||||
|
err := grpcClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
}(grpcClient)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
err = grpcClient.CheckServerHealth(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("gRPC health check failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
ctx, cancel = context.WithCancel(context.Background())
|
||||||
|
//go func(err error) {
|
||||||
|
// if !errors.Is(err, ctx.Err()) {
|
||||||
|
// log.Fatalf("Event subscription error: %s", err)
|
||||||
|
// }
|
||||||
|
//}(grpcClient.SubscribeEvents(ctx))
|
||||||
|
go func() {
|
||||||
|
err := grpcClient.SubscribeEvents(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
app, err := server.NewServer(sshConfig, sessionRegistry, grpcClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to start server: %s", err)
|
log.Fatalf("Failed to start server: %s", err)
|
||||||
}
|
}
|
||||||
app.Start()
|
app.Start()
|
||||||
|
cancel()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"tunnel_pls/internal/config"
|
"tunnel_pls/internal/config"
|
||||||
|
"tunnel_pls/internal/grpc/client"
|
||||||
"tunnel_pls/session"
|
"tunnel_pls/session"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@@ -14,9 +15,10 @@ type Server struct {
|
|||||||
conn *net.Listener
|
conn *net.Listener
|
||||||
config *ssh.ServerConfig
|
config *ssh.ServerConfig
|
||||||
sessionRegistry session.Registry
|
sessionRegistry session.Registry
|
||||||
|
grpcClient *client.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(sshConfig *ssh.ServerConfig, sessionRegistry session.Registry) (*Server, error) {
|
func NewServer(sshConfig *ssh.ServerConfig, sessionRegistry session.Registry, grpcClient *client.Client) (*Server, error) {
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%s", config.Getenv("PORT", "2200")))
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%s", config.Getenv("PORT", "2200")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to listen on port 2200: %v", err)
|
log.Fatalf("failed to listen on port 2200: %v", err)
|
||||||
@@ -42,6 +44,7 @@ func NewServer(sshConfig *ssh.ServerConfig, sessionRegistry session.Registry) (*
|
|||||||
conn: &listener,
|
conn: &listener,
|
||||||
config: sshConfig,
|
config: sshConfig,
|
||||||
sessionRegistry: sessionRegistry,
|
sessionRegistry: sessionRegistry,
|
||||||
|
grpcClient: grpcClient,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +72,13 @@ func (s *Server) handleConnection(conn net.Conn) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//ctx := context.Background()
|
||||||
log.Println("SSH connection established:", sshConn.User())
|
//log.Println("SSH connection established:", sshConn.User())
|
||||||
|
//get, err := s.grpcClient.IdentityService.Get(ctx, &gen.IdentifierRequest{Id: sshConn.User()})
|
||||||
|
//if err != nil {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//fmt.Println(get)
|
||||||
sshSession := session.New(sshConn, forwardingReqs, chans, s.sessionRegistry)
|
sshSession := session.New(sshConn, forwardingReqs, chans, s.sessionRegistry)
|
||||||
err = sshSession.Start()
|
err = sshSession.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type Controller interface {
|
|||||||
SetSlugModificator(func(oldSlug, newSlug string) bool)
|
SetSlugModificator(func(oldSlug, newSlug string) bool)
|
||||||
Start()
|
Start()
|
||||||
SetWH(w, h int)
|
SetWH(w, h int)
|
||||||
|
Redraw()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Forwarder interface {
|
type Forwarder interface {
|
||||||
@@ -65,7 +66,6 @@ type commandItem struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
tunnelURL string
|
|
||||||
domain string
|
domain string
|
||||||
protocol string
|
protocol string
|
||||||
tunnelType types.TunnelType
|
tunnelType types.TunnelType
|
||||||
@@ -84,6 +84,13 @@ type model struct {
|
|||||||
height int
|
height int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *model) getTunnelURL() string {
|
||||||
|
if m.tunnelType == types.HTTP {
|
||||||
|
return buildURL(m.protocol, m.interaction.slugManager.Get(), m.domain)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("tcp://%s:%d", m.domain, m.port)
|
||||||
|
}
|
||||||
|
|
||||||
type keymap struct {
|
type keymap struct {
|
||||||
quit key.Binding
|
quit key.Binding
|
||||||
command key.Binding
|
command key.Binding
|
||||||
@@ -163,11 +170,11 @@ func tickCmd(d time.Duration) tea.Cmd {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
func (m *model) Init() tea.Cmd {
|
||||||
return tea.Batch(textinput.Blink, tea.WindowSize())
|
return tea.Batch(textinput.Blink, tea.WindowSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
@@ -225,7 +232,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
m.tunnelURL = buildURL(m.protocol, inputValue, m.domain)
|
|
||||||
m.editingSlug = false
|
m.editingSlug = false
|
||||||
m.slugError = ""
|
m.slugError = ""
|
||||||
return m, tea.Batch(tea.ClearScreen, textinput.Blink)
|
return m, tea.Batch(tea.ClearScreen, textinput.Blink)
|
||||||
@@ -291,14 +297,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) helpView() string {
|
func (i *Interaction) Redraw() {
|
||||||
|
if i.program != nil {
|
||||||
|
i.program.Send(tea.ClearScreen())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) helpView() string {
|
||||||
return "\n" + m.help.ShortHelpView([]key.Binding{
|
return "\n" + m.help.ShortHelpView([]key.Binding{
|
||||||
m.keymap.command,
|
m.keymap.command,
|
||||||
m.keymap.quit,
|
m.keymap.quit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) View() string {
|
func (m *model) View() string {
|
||||||
if m.quitting {
|
if m.quitting {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -659,11 +671,11 @@ func (m model) View() string {
|
|||||||
MarginBottom(boxMargin).
|
MarginBottom(boxMargin).
|
||||||
Width(boxMaxWidth)
|
Width(boxMaxWidth)
|
||||||
|
|
||||||
urlDisplay := m.tunnelURL
|
urlDisplay := m.getTunnelURL()
|
||||||
if shouldUseCompactLayout(m.width, 80) && len(m.tunnelURL) > m.width-20 {
|
if shouldUseCompactLayout(m.width, 80) && len(urlDisplay) > m.width-20 {
|
||||||
maxLen := m.width - 25
|
maxLen := m.width - 25
|
||||||
if maxLen > 10 {
|
if maxLen > 10 {
|
||||||
urlDisplay = truncateString(m.tunnelURL, maxLen)
|
urlDisplay = truncateString(urlDisplay, maxLen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,13 +749,6 @@ func (i *Interaction) Start() {
|
|||||||
tunnelType := i.forwarder.GetTunnelType()
|
tunnelType := i.forwarder.GetTunnelType()
|
||||||
port := i.forwarder.GetForwardedPort()
|
port := i.forwarder.GetForwardedPort()
|
||||||
|
|
||||||
var tunnelURL string
|
|
||||||
if tunnelType == types.HTTP {
|
|
||||||
tunnelURL = buildURL(protocol, i.slugManager.Get(), domain)
|
|
||||||
} else {
|
|
||||||
tunnelURL = fmt.Sprintf("tcp://%s:%d", domain, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
items := []list.Item{
|
items := []list.Item{
|
||||||
commandItem{name: "slug", desc: "Set custom subdomain"},
|
commandItem{name: "slug", desc: "Set custom subdomain"},
|
||||||
commandItem{name: "tunnel-type", desc: "Change tunnel type (Coming Soon)"},
|
commandItem{name: "tunnel-type", desc: "Change tunnel type (Coming Soon)"},
|
||||||
@@ -764,8 +769,7 @@ func (i *Interaction) Start() {
|
|||||||
ti.CharLimit = 20
|
ti.CharLimit = 20
|
||||||
ti.Width = 50
|
ti.Width = 50
|
||||||
|
|
||||||
m := model{
|
m := &model{
|
||||||
tunnelURL: tunnelURL,
|
|
||||||
domain: domain,
|
domain: domain,
|
||||||
protocol: protocol,
|
protocol: protocol,
|
||||||
tunnelType: tunnelType,
|
tunnelType: tunnelType,
|
||||||
|
|||||||
Reference in New Issue
Block a user