Compare commits
5 Commits
7357cebc34
...
4ff4d37442
Author | SHA1 | Date | |
---|---|---|---|
|
4ff4d37442 | ||
|
f611f9dadc | ||
|
bb65396df6 | ||
|
5695d6b2e2 | ||
|
41768cbec9 |
35
playground/deploy-k8s/README.md
Normal file
35
playground/deploy-k8s/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# K8S部署指南
|
||||||
|
|
||||||
|
## 环境准备
|
||||||
|
|
||||||
|
1、准备一个标准的Kubernetes集群。
|
||||||
|
2、创建一个storageClassName,譬如 cbs-next-terminal
|
||||||
|
3、创建一个命名空间 next-terminal
|
||||||
|
|
||||||
|
## 启动next-terminal
|
||||||
|
1、根据需要修改相关参数,譬如PV卷的大小,端口。
|
||||||
|
2、执行start.sh,如下所示:
|
||||||
|
- kubectl apply -f mysql-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
- kubectl apply -f mysql-deployment.yaml -n next-terminal
|
||||||
|
- kubectl apply -f mysql-service.yaml -n next-terminal
|
||||||
|
- kubectl apply -f guacd-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
- kubectl apply -f guacd-deployment.yaml -n next-terminal
|
||||||
|
- kubectl apply -f guacd-service.yaml -n next-terminal
|
||||||
|
- kubectl apply -f next-terminal-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
- kubectl apply -f next-terminal-claim1-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
- kubectl apply -f next-terminal-deployment.yaml -n next-terminal
|
||||||
|
- kubectl apply -f next-terminal-service.yaml -n next-terminal
|
||||||
|
3、在Kubernetes集群中查询service,可以增加ingress配置,进行访问。
|
||||||
|
|
||||||
|
## 销毁next-terminal
|
||||||
|
1、执行stop.sh,如下所示:
|
||||||
|
- kubectl delete -f mysql-service.yaml -n next-terminal
|
||||||
|
- kubectl delete -f mysql-deployment.yaml -n next-terminal
|
||||||
|
- kubectl delete -f mysql-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
- kubectl delete -f guacd-service.yaml -n next-terminal
|
||||||
|
- kubectl delete -f guacd-deployment.yaml -n next-terminal
|
||||||
|
- kubectl delete -f guacd-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
- kubectl delete -f next-terminal-service.yaml -n next-terminal
|
||||||
|
- kubectl delete -f next-terminal-deployment.yaml -n next-terminal
|
||||||
|
- kubectl delete -f next-terminal-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
- kubectl delete -f next-terminal-claim1-persistentvolumeclaim.yaml -n next-terminal
|
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: guacd-claim0
|
||||||
|
name: guacd-claim0
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: cbs-next-terminal
|
||||||
|
status: {}
|
39
playground/deploy-k8s/guacd-deployment.yaml
Normal file
39
playground/deploy-k8s/guacd-deployment.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: guacd
|
||||||
|
name: guacd
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
io.kompose.service: guacd
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: guacd
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: dushixiang/guacd:latest
|
||||||
|
name: guacd
|
||||||
|
resources: {}
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /usr/local/next-terminal/data
|
||||||
|
name: guacd-claim0
|
||||||
|
restartPolicy: Always
|
||||||
|
volumes:
|
||||||
|
- name: guacd-claim0
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: guacd-claim0
|
||||||
|
status: {}
|
19
playground/deploy-k8s/guacd-service.yaml
Normal file
19
playground/deploy-k8s/guacd-service.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: guacd
|
||||||
|
name: guacd
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: "4822"
|
||||||
|
port: 4822
|
||||||
|
targetPort: 4822
|
||||||
|
selector:
|
||||||
|
io.kompose.service: guacd
|
||||||
|
status:
|
||||||
|
loadBalancer: {}
|
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: mysql-claim0
|
||||||
|
name: mysql-claim0
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: cbs-next-terminal
|
||||||
|
status: {}
|
48
playground/deploy-k8s/mysql-deployment.yaml
Normal file
48
playground/deploy-k8s/mysql-deployment.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: mysql
|
||||||
|
name: mysql
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
io.kompose.service: mysql
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: mysql
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: MYSQL_DATABASE
|
||||||
|
value: next-terminal
|
||||||
|
- name: MYSQL_PASSWORD
|
||||||
|
value: next-terminal
|
||||||
|
- name: MYSQL_ROOT_PASSWORD
|
||||||
|
value: next-terminal
|
||||||
|
- name: MYSQL_USER
|
||||||
|
value: next-terminal
|
||||||
|
image: mysql:8.0
|
||||||
|
name: mysql
|
||||||
|
resources: {}
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /var/lib/mysql
|
||||||
|
name: mysql-claim0
|
||||||
|
restartPolicy: Always
|
||||||
|
volumes:
|
||||||
|
- name: mysql-claim0
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: mysql-claim0
|
||||||
|
status: {}
|
19
playground/deploy-k8s/mysql-service.yaml
Normal file
19
playground/deploy-k8s/mysql-service.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: mysql
|
||||||
|
name: mysql
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: "3306"
|
||||||
|
port: 3306
|
||||||
|
targetPort: 3306
|
||||||
|
selector:
|
||||||
|
io.kompose.service: mysql
|
||||||
|
status:
|
||||||
|
loadBalancer: {}
|
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: next-terminal-claim0
|
||||||
|
name: next-terminal-claim0
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: cbs-next-terminal
|
||||||
|
status: {}
|
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: next-terminal-claim1
|
||||||
|
name: next-terminal-claim1
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: cbs-next-terminal
|
||||||
|
status: {}
|
63
playground/deploy-k8s/next-terminal-deployment.yaml
Normal file
63
playground/deploy-k8s/next-terminal-deployment.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: next-terminal
|
||||||
|
name: next-terminal
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
io.kompose.service: next-terminal
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: next-terminal
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: DB
|
||||||
|
value: mysql
|
||||||
|
- name: GUACD_HOSTNAME
|
||||||
|
value: guacd
|
||||||
|
- name: GUACD_PORT
|
||||||
|
value: "4822"
|
||||||
|
- name: MYSQL_DATABASE
|
||||||
|
value: next-terminal
|
||||||
|
- name: MYSQL_HOSTNAME
|
||||||
|
value: mysql
|
||||||
|
- name: MYSQL_PASSWORD
|
||||||
|
value: next-terminal
|
||||||
|
- name: MYSQL_PORT
|
||||||
|
value: "3306"
|
||||||
|
- name: MYSQL_USERNAME
|
||||||
|
value: next-terminal
|
||||||
|
image: dushixiang/next-terminal:latest
|
||||||
|
name: next-terminal
|
||||||
|
ports:
|
||||||
|
- containerPort: 8088
|
||||||
|
resources: {}
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /etc
|
||||||
|
name: next-terminal-claim0
|
||||||
|
- mountPath: /usr/local/next-terminal/data
|
||||||
|
name: next-terminal-claim1
|
||||||
|
restartPolicy: Always
|
||||||
|
volumes:
|
||||||
|
- name: next-terminal-claim0
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: next-terminal-claim0
|
||||||
|
- name: next-terminal-claim1
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: next-terminal-claim1
|
||||||
|
status: {}
|
19
playground/deploy-k8s/next-terminal-service.yaml
Normal file
19
playground/deploy-k8s/next-terminal-service.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kompose.cmd: kompose convert
|
||||||
|
kompose.version: 1.26.1 (a9d05d509)
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
io.kompose.service: next-terminal
|
||||||
|
name: next-terminal
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: "8088"
|
||||||
|
port: 8088
|
||||||
|
targetPort: 8088
|
||||||
|
selector:
|
||||||
|
io.kompose.service: next-terminal
|
||||||
|
status:
|
||||||
|
loadBalancer: {}
|
10
playground/deploy-k8s/start.sh
Normal file
10
playground/deploy-k8s/start.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
kubectl apply -f mysql-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
kubectl apply -f mysql-deployment.yaml -n next-terminal
|
||||||
|
kubectl apply -f mysql-service.yaml -n next-terminal
|
||||||
|
kubectl apply -f guacd-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
kubectl apply -f guacd-deployment.yaml -n next-terminal
|
||||||
|
kubectl apply -f guacd-service.yaml -n next-terminal
|
||||||
|
kubectl apply -f next-terminal-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
kubectl apply -f next-terminal-claim1-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
kubectl apply -f next-terminal-deployment.yaml -n next-terminal
|
||||||
|
kubectl apply -f next-terminal-service.yaml -n next-terminal
|
11
playground/deploy-k8s/stop.sh
Normal file
11
playground/deploy-k8s/stop.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
kubectl delete -f mysql-service.yaml -n next-terminal
|
||||||
|
kubectl delete -f mysql-deployment.yaml -n next-terminal
|
||||||
|
kubectl delete -f mysql-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
kubectl delete -f guacd-service.yaml -n next-terminal
|
||||||
|
kubectl delete -f guacd-deployment.yaml -n next-terminal
|
||||||
|
kubectl delete -f guacd-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
kubectl delete -f next-terminal-service.yaml -n next-terminal
|
||||||
|
kubectl delete -f next-terminal-deployment.yaml -n next-terminal
|
||||||
|
kubectl delete -f next-terminal-claim0-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
kubectl delete -f next-terminal-claim1-persistentvolumeclaim.yaml -n next-terminal
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"next-terminal/server/global/gateway"
|
||||||
"next-terminal/server/model"
|
"next-terminal/server/model"
|
||||||
"next-terminal/server/repository"
|
"next-terminal/server/repository"
|
||||||
"next-terminal/server/service"
|
"next-terminal/server/service"
|
||||||
@ -28,7 +29,7 @@ func (api AccessGatewayApi) AccessGatewayCreateEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 连接网关
|
// 连接网关
|
||||||
service.GatewayService.ReConnect(&item)
|
service.GatewayService.ReLoad(&item)
|
||||||
return Success(c, "")
|
return Success(c, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,13 +59,12 @@ func (api AccessGatewayApi) AccessGatewayPagingEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for i := 0; i < len(items); i++ {
|
for i := 0; i < len(items); i++ {
|
||||||
g, err := service.GatewayService.GetGatewayById(items[i].ID)
|
g := gateway.GlobalGatewayManager.GetById(items[i].ID)
|
||||||
if err != nil {
|
if g != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
items[i].Connected = g.Connected
|
items[i].Connected = g.Connected
|
||||||
items[i].Message = g.Message
|
items[i].Message = g.Message
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Success(c, Map{
|
return Success(c, Map{
|
||||||
"total": total,
|
"total": total,
|
||||||
@ -83,7 +83,7 @@ func (api AccessGatewayApi) AccessGatewayUpdateEndpoint(c echo.Context) error {
|
|||||||
if err := repository.GatewayRepository.UpdateById(context.TODO(), &item, id); err != nil {
|
if err := repository.GatewayRepository.UpdateById(context.TODO(), &item, id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
service.GatewayService.ReConnect(&item)
|
service.GatewayService.ReLoad(&item)
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,14 +110,3 @@ func (api AccessGatewayApi) AccessGatewayGetEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
return Success(c, item)
|
return Success(c, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api AccessGatewayApi) AccessGatewayReconnectEndpoint(c echo.Context) error {
|
|
||||||
id := c.Param("id")
|
|
||||||
|
|
||||||
item, err := repository.GatewayRepository.FindById(context.TODO(), id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
service.GatewayService.ReConnect(&item)
|
|
||||||
return Success(c, "")
|
|
||||||
}
|
|
||||||
|
@ -72,15 +72,13 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
|
|||||||
api.setConfig(propertyMap, s, configuration)
|
api.setConfig(propertyMap, s, configuration)
|
||||||
|
|
||||||
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
||||||
g, err := service.GatewayService.GetGatewayAndReconnectById(s.AccessGatewayId)
|
g, err := service.GatewayService.GetGatewayById(s.AccessGatewayId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Disconnect(ws, AccessGatewayUnAvailable, "获取接入网关失败:"+err.Error())
|
utils.Disconnect(ws, AccessGatewayUnAvailable, "获取接入网关失败:"+err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !g.Connected {
|
|
||||||
utils.Disconnect(ws, AccessGatewayUnAvailable, "接入网关不可用:"+g.Message)
|
defer g.CloseSshTunnel(s.ID)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, s.IP, s.Port)
|
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, s.IP, s.Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败:"+err.Error())
|
utils.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败:"+err.Error())
|
||||||
@ -88,7 +86,6 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
s.IP = exposedIP
|
s.IP = exposedIP
|
||||||
s.Port = exposedPort
|
s.Port = exposedPort
|
||||||
defer g.CloseSshTunnel(s.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration.SetParameter("hostname", s.IP)
|
configuration.SetParameter("hostname", s.IP)
|
||||||
|
@ -70,18 +70,16 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
||||||
g, err := service.GatewayService.GetGatewayAndReconnectById(s.AccessGatewayId)
|
g, err := service.GatewayService.GetGatewayById(s.AccessGatewayId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return WriteMessage(ws, dto.NewMessage(Closed, "获取接入网关失败:"+err.Error()))
|
return WriteMessage(ws, dto.NewMessage(Closed, "获取接入网关失败:"+err.Error()))
|
||||||
}
|
}
|
||||||
if !g.Connected {
|
|
||||||
return WriteMessage(ws, dto.NewMessage(Closed, "接入网关不可用:"+g.Message))
|
defer g.CloseSshTunnel(s.ID)
|
||||||
}
|
|
||||||
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
|
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return WriteMessage(ws, dto.NewMessage(Closed, "创建隧道失败:"+err.Error()))
|
return WriteMessage(ws, dto.NewMessage(Closed, "创建隧道失败:"+err.Error()))
|
||||||
}
|
}
|
||||||
defer g.CloseSshTunnel(s.ID)
|
|
||||||
ip = exposedIP
|
ip = exposedIP
|
||||||
port = exposedPort
|
port = exposedPort
|
||||||
}
|
}
|
||||||
|
@ -83,13 +83,8 @@ func (r *TermHandler) writeToWebsocket() {
|
|||||||
if r.isRecording {
|
if r.isRecording {
|
||||||
_ = r.nextTerminal.Recorder.WriteData(s)
|
_ = r.nextTerminal.Recorder.WriteData(s)
|
||||||
}
|
}
|
||||||
nextSession := session.GlobalSessionManager.GetById(r.sessionId)
|
|
||||||
// 监控
|
// 监控
|
||||||
if nextSession != nil && nextSession.Observer != nil {
|
SendObData(r.sessionId, s)
|
||||||
nextSession.Observer.Range(func(key string, ob *session.Session) {
|
|
||||||
_ = ob.WriteMessage(dto.NewMessage(Data, s))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
buf = []byte{}
|
buf = []byte{}
|
||||||
}
|
}
|
||||||
case data := <-r.dataChan:
|
case data := <-r.dataChan:
|
||||||
@ -113,3 +108,12 @@ func (r *TermHandler) WriteMessage(msg dto.Message) error {
|
|||||||
message := []byte(msg.ToString())
|
message := []byte(msg.ToString())
|
||||||
return r.webSocket.WriteMessage(websocket.TextMessage, message)
|
return r.webSocket.WriteMessage(websocket.TextMessage, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SendObData(sessionId, s string) {
|
||||||
|
nextSession := session.GlobalSessionManager.GetById(sessionId)
|
||||||
|
if nextSession != nil && nextSession.Observer != nil {
|
||||||
|
nextSession.Observer.Range(func(key string, ob *session.Session) {
|
||||||
|
_ = ob.WriteMessage(dto.NewMessage(Data, s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,7 +36,7 @@ func (app App) InitDBData() (err error) {
|
|||||||
if err := service.PropertyService.DeleteDeprecatedProperty(); err != nil {
|
if err := service.PropertyService.DeleteDeprecatedProperty(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := service.GatewayService.ReConnectAll(); err != nil {
|
if err := service.GatewayService.LoadAll(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := service.PropertyService.InitProperties(); err != nil {
|
if err := service.PropertyService.InitProperties(); err != nil {
|
||||||
|
@ -271,7 +271,6 @@ func setupRoutes() *echo.Echo {
|
|||||||
accessGateways.PUT("/:id", AccessGatewayApi.AccessGatewayUpdateEndpoint)
|
accessGateways.PUT("/:id", AccessGatewayApi.AccessGatewayUpdateEndpoint)
|
||||||
accessGateways.DELETE("/:id", AccessGatewayApi.AccessGatewayDeleteEndpoint)
|
accessGateways.DELETE("/:id", AccessGatewayApi.AccessGatewayDeleteEndpoint)
|
||||||
accessGateways.GET("/:id", AccessGatewayApi.AccessGatewayGetEndpoint)
|
accessGateways.GET("/:id", AccessGatewayApi.AccessGatewayGetEndpoint)
|
||||||
accessGateways.POST("/:id/reconnect", AccessGatewayApi.AccessGatewayReconnectEndpoint)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
backup := e.Group("/backup", Admin)
|
backup := e.Group("/backup", Admin)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"next-terminal/server/term"
|
||||||
"next-terminal/server/utils"
|
"next-terminal/server/utils"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@ -16,32 +16,34 @@ import (
|
|||||||
// Gateway 接入网关
|
// Gateway 接入网关
|
||||||
type Gateway struct {
|
type Gateway struct {
|
||||||
ID string // 接入网关ID
|
ID string // 接入网关ID
|
||||||
|
IP string
|
||||||
|
Port int
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
PrivateKey string
|
||||||
|
Passphrase string
|
||||||
Connected bool // 是否已连接
|
Connected bool // 是否已连接
|
||||||
SshClient *ssh.Client
|
|
||||||
Message string // 失败原因
|
Message string // 失败原因
|
||||||
|
SshClient *ssh.Client
|
||||||
|
|
||||||
tunnels sync.Map
|
mutex sync.Mutex
|
||||||
}
|
tunnels map[string]*Tunnel
|
||||||
|
|
||||||
func NewGateway(id string, connected bool, message string, client *ssh.Client) *Gateway {
|
|
||||||
return &Gateway{
|
|
||||||
ID: id,
|
|
||||||
Connected: connected,
|
|
||||||
Message: message,
|
|
||||||
SshClient: client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Gateway) Close() {
|
|
||||||
g.tunnels.Range(func(key, value interface{}) bool {
|
|
||||||
g.CloseSshTunnel(key.(string))
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gateway) OpenSshTunnel(id, ip string, port int) (exposedIP string, exposedPort int, err error) {
|
func (g *Gateway) OpenSshTunnel(id, ip string, port int) (exposedIP string, exposedPort int, err error) {
|
||||||
|
g.mutex.Lock()
|
||||||
|
defer g.mutex.Unlock()
|
||||||
if !g.Connected {
|
if !g.Connected {
|
||||||
|
sshClient, err := term.NewSshClient(g.IP, g.Port, g.Username, g.Password, g.PrivateKey, g.Passphrase)
|
||||||
|
if err != nil {
|
||||||
|
g.Connected = false
|
||||||
|
g.Message = "接入网关不可用:" + err.Error()
|
||||||
return "", 0, errors.New(g.Message)
|
return "", 0, errors.New(g.Message)
|
||||||
|
} else {
|
||||||
|
g.Connected = true
|
||||||
|
g.SshClient = sshClient
|
||||||
|
g.Message = "使用中"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localPort, err := utils.GetAvailablePort()
|
localPort, err := utils.GetAvailablePort()
|
||||||
@ -63,30 +65,39 @@ func (g *Gateway) OpenSshTunnel(id, ip string, port int) (exposedIP string, expo
|
|||||||
return "", 0, err
|
return "", 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
tunnel := &Tunnel{
|
tunnel := &Tunnel{
|
||||||
ID: id,
|
id: id,
|
||||||
LocalHost: hostname,
|
localHost: hostname,
|
||||||
//LocalHost: "docker.for.mac.host.internal",
|
//localHost: "docker.for.mac.host.internal",
|
||||||
LocalPort: localPort,
|
localPort: localPort,
|
||||||
Gateway: g,
|
remoteHost: ip,
|
||||||
RemoteHost: ip,
|
remotePort: port,
|
||||||
RemotePort: port,
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
listener: listener,
|
listener: listener,
|
||||||
}
|
}
|
||||||
go tunnel.Open()
|
go tunnel.Open(g.SshClient)
|
||||||
g.tunnels.Store(tunnel.ID, tunnel)
|
g.tunnels[tunnel.id] = tunnel
|
||||||
|
|
||||||
return tunnel.LocalHost, tunnel.LocalPort, nil
|
return tunnel.localHost, tunnel.localPort, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gateway) CloseSshTunnel(id string) {
|
func (g *Gateway) CloseSshTunnel(id string) {
|
||||||
if value, ok := g.tunnels.Load(id); ok {
|
g.mutex.Lock()
|
||||||
if tunnel, vok := value.(*Tunnel); vok {
|
defer g.mutex.Unlock()
|
||||||
tunnel.Close()
|
t := g.tunnels[id]
|
||||||
g.tunnels.Delete(id)
|
if t != nil {
|
||||||
|
t.Close()
|
||||||
|
delete(g.tunnels, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.tunnels) == 0 {
|
||||||
|
_ = g.SshClient.Close()
|
||||||
|
g.Connected = false
|
||||||
|
g.Message = "暂未使用"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) Close() {
|
||||||
|
for id := range g.tunnels {
|
||||||
|
g.CloseSshTunnel(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,35 +4,50 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"next-terminal/server/log"
|
"next-terminal/server/log"
|
||||||
|
"next-terminal/server/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type manager struct {
|
||||||
gateways sync.Map
|
gateways sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func (m *manager) GetById(id string) *Gateway {
|
||||||
return &Manager{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) GetById(id string) *Gateway {
|
|
||||||
if val, ok := m.gateways.Load(id); ok {
|
if val, ok := m.gateways.Load(id); ok {
|
||||||
return val.(*Gateway)
|
return val.(*Gateway)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Add(g *Gateway) {
|
func (m *manager) Add(model *model.AccessGateway) *Gateway {
|
||||||
|
g := &Gateway{
|
||||||
|
ID: model.ID,
|
||||||
|
IP: model.IP,
|
||||||
|
Port: model.Port,
|
||||||
|
Username: model.Username,
|
||||||
|
Password: model.Password,
|
||||||
|
PrivateKey: model.PrivateKey,
|
||||||
|
Passphrase: model.Passphrase,
|
||||||
|
Connected: false,
|
||||||
|
SshClient: nil,
|
||||||
|
Message: "暂未使用",
|
||||||
|
tunnels: make(map[string]*Tunnel),
|
||||||
|
}
|
||||||
m.gateways.Store(g.ID, g)
|
m.gateways.Store(g.ID, g)
|
||||||
log.Infof("add gateway: %s", g.ID)
|
log.Infof("add Gateway: %s", g.ID)
|
||||||
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Del(id string) {
|
func (m *manager) Del(id string) {
|
||||||
|
g := m.GetById(id)
|
||||||
|
if g != nil {
|
||||||
|
g.Close()
|
||||||
|
}
|
||||||
m.gateways.Delete(id)
|
m.gateways.Delete(id)
|
||||||
log.Infof("del gateway: %s", id)
|
log.Infof("del Gateway: %s", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
var GlobalGatewayManager *Manager
|
var GlobalGatewayManager *manager
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
GlobalGatewayManager = NewManager()
|
GlobalGatewayManager = &manager{}
|
||||||
}
|
}
|
||||||
|
@ -1,59 +1,55 @@
|
|||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"next-terminal/server/log"
|
"next-terminal/server/log"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tunnel struct {
|
type Tunnel struct {
|
||||||
ID string // 唯一标识
|
id string // 唯一标识
|
||||||
LocalHost string // 本地监听地址
|
localHost string // 本地监听地址
|
||||||
LocalPort int // 本地端口
|
localPort int // 本地端口
|
||||||
RemoteHost string // 远程连接地址
|
remoteHost string // 远程连接地址
|
||||||
RemotePort int // 远程端口
|
remotePort int // 远程端口
|
||||||
Gateway *Gateway
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
localConnections []net.Conn
|
localConnections []net.Conn
|
||||||
remoteConnections []net.Conn
|
remoteConnections []net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Tunnel) Open() {
|
func (r *Tunnel) Open(sshClient *ssh.Client) {
|
||||||
localAddr := fmt.Sprintf("%s:%d", r.LocalHost, r.LocalPort)
|
localAddr := fmt.Sprintf("%s:%d", r.localHost, r.localPort)
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-r.ctx.Done()
|
|
||||||
_ = r.listener.Close()
|
|
||||||
log.Debugf("SSH 隧道 %v 关闭", localAddr)
|
|
||||||
}()
|
|
||||||
for {
|
for {
|
||||||
log.Debugf("等待客户端访问 %v", localAddr)
|
log.Debugf("隧道 %v 等待客户端访问 %v", r.id, localAddr)
|
||||||
localConn, err := r.listener.Accept()
|
localConn, err := r.listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("接受连接失败 %v, 退出循环", err.Error())
|
log.Debugf("隧道 %v 接受连接失败 %v, 退出循环", r.id, err.Error())
|
||||||
|
log.Debug("-------------------------------------------------")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.localConnections = append(r.localConnections, localConn)
|
r.localConnections = append(r.localConnections, localConn)
|
||||||
|
|
||||||
log.Debugf("客户端 %v 连接至 %v", localConn.RemoteAddr().String(), localAddr)
|
log.Debugf("隧道 %v 新增本地连接 %v", r.id, localConn.RemoteAddr().String())
|
||||||
remoteAddr := fmt.Sprintf("%s:%d", r.RemoteHost, r.RemotePort)
|
remoteAddr := fmt.Sprintf("%s:%d", r.remoteHost, r.remotePort)
|
||||||
log.Debugf("连接远程主机 %v ...", remoteAddr)
|
log.Debugf("隧道 %v 连接远程地址 %v ...", r.id, remoteAddr)
|
||||||
remoteConn, err := r.Gateway.SshClient.Dial("tcp", remoteAddr)
|
remoteConn, err := sshClient.Dial("tcp", remoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("连接远程主机 %v 失败", remoteAddr)
|
log.Debugf("隧道 %v 连接远程地址 %v, 退出循环", r.id, err.Error())
|
||||||
|
log.Debug("-------------------------------------------------")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.remoteConnections = append(r.remoteConnections, remoteConn)
|
r.remoteConnections = append(r.remoteConnections, remoteConn)
|
||||||
|
|
||||||
log.Debugf("连接远程主机 %v 成功", remoteAddr)
|
log.Debugf("隧道 %v 连接远程主机成功", r.id)
|
||||||
go copyConn(localConn, remoteConn)
|
go copyConn(localConn, remoteConn)
|
||||||
go copyConn(remoteConn, localConn)
|
go copyConn(remoteConn, localConn)
|
||||||
log.Debugf("转发数据 [%v]->[%v]", localAddr, remoteAddr)
|
log.Debugf("隧道 %v 开始转发数据 [%v]->[%v]", r.id, localAddr, remoteAddr)
|
||||||
|
log.Debug("~~~~~~~~~~~~~~~~~~~~分割线~~~~~~~~~~~~~~~~~~~~~~~~")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +62,8 @@ func (r *Tunnel) Close() {
|
|||||||
_ = r.remoteConnections[i].Close()
|
_ = r.remoteConnections[i].Close()
|
||||||
}
|
}
|
||||||
r.remoteConnections = nil
|
r.remoteConnections = nil
|
||||||
r.cancel()
|
_ = r.listener.Close()
|
||||||
|
log.Debugf("隧道 %v 监听器关闭", r.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyConn(writer, reader net.Conn) {
|
func copyConn(writer, reader net.Conn) {
|
||||||
|
@ -116,29 +116,24 @@ func (s assetService) FindByIdAndDecrypt(c context.Context, id string) (model.As
|
|||||||
return asset, nil
|
return asset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s assetService) CheckStatus(accessGatewayId string, ip string, port int) (active bool, err error) {
|
func (s assetService) CheckStatus(accessGatewayId string, ip string, port int) (bool, error) {
|
||||||
if accessGatewayId != "" && accessGatewayId != "-" {
|
if accessGatewayId != "" && accessGatewayId != "-" {
|
||||||
g, e1 := GatewayService.GetGatewayAndReconnectById(accessGatewayId)
|
g, err := GatewayService.GetGatewayById(accessGatewayId)
|
||||||
if e1 != nil {
|
if err != nil {
|
||||||
return false, e1
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
uuid := utils.UUID()
|
uuid := utils.UUID()
|
||||||
exposedIP, exposedPort, e2 := g.OpenSshTunnel(uuid, ip, port)
|
|
||||||
if e2 != nil {
|
|
||||||
return false, e2
|
|
||||||
}
|
|
||||||
defer g.CloseSshTunnel(uuid)
|
defer g.CloseSshTunnel(uuid)
|
||||||
|
exposedIP, exposedPort, err := g.OpenSshTunnel(uuid, ip, port)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
if g.Connected {
|
return utils.Tcping(exposedIP, exposedPort)
|
||||||
active, err = utils.Tcping(exposedIP, exposedPort)
|
|
||||||
} else {
|
|
||||||
active = false
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
active, err = utils.Tcping(ip, port)
|
return utils.Tcping(ip, port)
|
||||||
}
|
|
||||||
return active, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s assetService) Create(ctx context.Context, m echo.Map) (model.Asset, error) {
|
func (s assetService) Create(ctx context.Context, m echo.Map) (model.Asset, error) {
|
||||||
@ -182,7 +177,7 @@ func (s assetService) create(c context.Context, item model.Asset, m echo.Map) er
|
|||||||
// active, _ := s.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
|
// active, _ := s.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
|
||||||
//
|
//
|
||||||
// if item.Active != active {
|
// if item.Active != active {
|
||||||
// _ = repository.AssetRepository.UpdateActiveById(context.TODO(), active, item.ID)
|
// _ = repository.AssetRepository.UpdateActiveById(context.TODO(), active, item.id)
|
||||||
// }
|
// }
|
||||||
//}()
|
//}()
|
||||||
return nil
|
return nil
|
||||||
|
@ -7,23 +7,10 @@ import (
|
|||||||
"next-terminal/server/log"
|
"next-terminal/server/log"
|
||||||
"next-terminal/server/model"
|
"next-terminal/server/model"
|
||||||
"next-terminal/server/repository"
|
"next-terminal/server/repository"
|
||||||
"next-terminal/server/term"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type gatewayService struct{}
|
type gatewayService struct{}
|
||||||
|
|
||||||
func (r gatewayService) GetGatewayAndReconnectById(accessGatewayId string) (g *gateway.Gateway, err error) {
|
|
||||||
g = gateway.GlobalGatewayManager.GetById(accessGatewayId)
|
|
||||||
if g == nil || !g.Connected {
|
|
||||||
accessGateway, err := repository.GatewayRepository.FindById(context.TODO(), accessGatewayId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
g = r.ReConnect(&accessGateway)
|
|
||||||
}
|
|
||||||
return g, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r gatewayService) GetGatewayById(accessGatewayId string) (g *gateway.Gateway, err error) {
|
func (r gatewayService) GetGatewayById(accessGatewayId string) (g *gateway.Gateway, err error) {
|
||||||
g = gateway.GlobalGatewayManager.GetById(accessGatewayId)
|
g = gateway.GlobalGatewayManager.GetById(accessGatewayId)
|
||||||
if g == nil {
|
if g == nil {
|
||||||
@ -31,40 +18,32 @@ func (r gatewayService) GetGatewayById(accessGatewayId string) (g *gateway.Gatew
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
g = r.ReConnect(&accessGateway)
|
g = r.ReLoad(&accessGateway)
|
||||||
}
|
}
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r gatewayService) ReConnectAll() error {
|
func (r gatewayService) LoadAll() error {
|
||||||
gateways, err := repository.GatewayRepository.FindAll(context.TODO())
|
gateways, err := repository.GatewayRepository.FindAll(context.TODO())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(gateways) > 0 {
|
if len(gateways) > 0 {
|
||||||
for i := range gateways {
|
for i := range gateways {
|
||||||
r.ReConnect(&gateways[i])
|
r.ReLoad(&gateways[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r gatewayService) ReConnect(m *model.AccessGateway) *gateway.Gateway {
|
func (r gatewayService) ReLoad(m *model.AccessGateway) *gateway.Gateway {
|
||||||
log.Debugf("重建接入网关「%v」中...", m.Name)
|
log.Debugf("重建接入网关「%v」中...", m.Name)
|
||||||
r.DisconnectById(m.ID)
|
r.DisconnectById(m.ID)
|
||||||
sshClient, err := term.NewSshClient(m.IP, m.Port, m.Username, m.Password, m.PrivateKey, m.Passphrase)
|
g := gateway.GlobalGatewayManager.Add(m)
|
||||||
var g *gateway.Gateway
|
|
||||||
if err != nil {
|
|
||||||
g = gateway.NewGateway(m.ID, false, err.Error(), nil)
|
|
||||||
} else {
|
|
||||||
g = gateway.NewGateway(m.ID, true, "", sshClient)
|
|
||||||
}
|
|
||||||
gateway.GlobalGatewayManager.Add(g)
|
|
||||||
log.Debugf("重建接入网关「%v」完成", m.Name)
|
log.Debugf("重建接入网关「%v」完成", m.Name)
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r gatewayService) DisconnectById(accessGatewayId string) {
|
func (r gatewayService) DisconnectById(id string) {
|
||||||
gateway.GlobalGatewayManager.Del(accessGatewayId)
|
gateway.GlobalGatewayManager.Del(id)
|
||||||
}
|
}
|
||||||
|
@ -125,16 +125,16 @@ func (r ShellJob) Run() {
|
|||||||
|
|
||||||
func exec(shell, accessGatewayId, ip string, port int, username, password, privateKey, passphrase string) (string, error) {
|
func exec(shell, accessGatewayId, ip string, port int, username, password, privateKey, passphrase string) (string, error) {
|
||||||
if accessGatewayId != "" && accessGatewayId != "-" {
|
if accessGatewayId != "" && accessGatewayId != "-" {
|
||||||
g, err := GatewayService.GetGatewayAndReconnectById(accessGatewayId)
|
g, err := GatewayService.GetGatewayById(accessGatewayId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
uuid := utils.UUID()
|
uuid := utils.UUID()
|
||||||
|
defer g.CloseSshTunnel(uuid)
|
||||||
exposedIP, exposedPort, err := g.OpenSshTunnel(uuid, ip, port)
|
exposedIP, exposedPort, err := g.OpenSshTunnel(uuid, ip, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer g.CloseSshTunnel(uuid)
|
|
||||||
return ExecCommandBySSH(shell, exposedIP, exposedPort, username, password, privateKey, passphrase)
|
return ExecCommandBySSH(shell, exposedIP, exposedPort, username, password, privateKey, passphrase)
|
||||||
} else {
|
} else {
|
||||||
return ExecCommandBySSH(shell, ip, port, username, password, privateKey, passphrase)
|
return ExecCommandBySSH(shell, ip, port, username, password, privateKey, passphrase)
|
||||||
|
@ -61,7 +61,7 @@ func (service userService) InitUser() (err error) {
|
|||||||
if err := repository.UserRepository.Update(context.TODO(), &user); err != nil {
|
if err := repository.UserRepository.Update(context.TODO(), &user); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Infof("自动修正用户「%v」ID「%v」类型为管理员", users[i].Nickname, users[i].ID)
|
log.Infof("自动修正用户「%v」id「%v」类型为管理员", users[i].Nickname, users[i].ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,18 +189,16 @@ func (gui Gui) handleAccessAsset(sess *ssh.Session, sessionId string) (err error
|
|||||||
)
|
)
|
||||||
|
|
||||||
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
||||||
g, err := service.GatewayService.GetGatewayAndReconnectById(s.AccessGatewayId)
|
g, err := service.GatewayService.GetGatewayById(s.AccessGatewayId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("获取接入网关失败:" + err.Error())
|
return errors.New("获取接入网关失败:" + err.Error())
|
||||||
}
|
}
|
||||||
if !g.Connected {
|
|
||||||
return errors.New("接入网关不可用:" + g.Message)
|
defer g.CloseSshTunnel(s.ID)
|
||||||
}
|
|
||||||
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
|
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("开启SSH隧道失败:" + err.Error())
|
return errors.New("开启SSH隧道失败:" + err.Error())
|
||||||
}
|
}
|
||||||
defer g.CloseSshTunnel(s.ID)
|
|
||||||
ip = exposedIP
|
ip = exposedIP
|
||||||
port = exposedPort
|
port = exposedPort
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"next-terminal/server/api"
|
"next-terminal/server/api"
|
||||||
"next-terminal/server/dto"
|
|
||||||
"next-terminal/server/global/session"
|
|
||||||
"next-terminal/server/term"
|
"next-terminal/server/term"
|
||||||
|
|
||||||
"github.com/gliderlabs/ssh"
|
"github.com/gliderlabs/ssh"
|
||||||
@ -51,24 +49,15 @@ func (w *Writer) Write(p []byte) (n int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
sendObData(w.sessionId, s)
|
api.SendObData(w.sessionId, s)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := w.recorder.WriteData(s)
|
err := w.recorder.WriteData(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
sendObData(w.sessionId, s)
|
api.SendObData(w.sessionId, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (*w.sess).Write(p)
|
return (*w.sess).Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendObData(sessionId, s string) {
|
|
||||||
nextSession := session.GlobalSessionManager.GetById(sessionId)
|
|
||||||
if nextSession != nil && nextSession.Observer != nil {
|
|
||||||
nextSession.Observer.Range(func(key string, ob *session.Session) {
|
|
||||||
_ = ob.WriteMessage(dto.NewMessage(api.Data, s))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -55,9 +55,9 @@ func (t *Ticker) deleteUnUsedSession() {
|
|||||||
err := repository.SessionRepository.DeleteById(context.TODO(), sessions[i].ID)
|
err := repository.SessionRepository.DeleteById(context.TODO(), sessions[i].ID)
|
||||||
s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port)
|
s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("会话「%v」ID「%v」超过1小时未打开,删除失败: %v", s, sessions[i].ID, err.Error())
|
log.Errorf("会话「%v」id「%v」超过1小时未打开,删除失败: %v", s, sessions[i].ID, err.Error())
|
||||||
} else {
|
} else {
|
||||||
log.Infof("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID)
|
log.Infof("会话「%v」id「%v」超过1小时未打开,已删除。", s, sessions[i].ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
server/utils/keyed_mutex.go
Normal file
15
server/utils/keyed_mutex.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type KeyedMutex struct {
|
||||||
|
mutexes sync.Map // Zero value is empty and ready for use
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *KeyedMutex) Lock(key string) func() {
|
||||||
|
value, _ := m.mutexes.LoadOrStore(key, &sync.Mutex{})
|
||||||
|
mtx := value.(*sync.Mutex)
|
||||||
|
mtx.Lock()
|
||||||
|
|
||||||
|
return func() { mtx.Unlock() }
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
.container > div {
|
|
||||||
margin: 0 auto;
|
|
||||||
cursor: none;
|
|
||||||
}
|
|
@ -141,29 +141,6 @@ class AccessGateway extends Component {
|
|||||||
await this.showModal('更新接入网关', result.data);
|
await this.showModal('更新接入网关', result.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reconnect(id, index) {
|
|
||||||
let items = this.state.items;
|
|
||||||
try {
|
|
||||||
items[index]['reconnectLoading'] = true;
|
|
||||||
this.setState({
|
|
||||||
items: items
|
|
||||||
});
|
|
||||||
message.info({content: '正在重连中...', key: id, duration: 5});
|
|
||||||
let result = await request.post(`/access-gateways/${id}/reconnect`);
|
|
||||||
if (result.code !== 1) {
|
|
||||||
message.error({content: result.message, key: id, duration: 10});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
message.success({content: '重连完成。', key: id, duration: 3});
|
|
||||||
this.loadTableData(this.state.queryParams);
|
|
||||||
} finally {
|
|
||||||
items[index]['reconnectLoading'] = false;
|
|
||||||
this.setState({
|
|
||||||
items: items
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showModal(title, obj) {
|
showModal(title, obj) {
|
||||||
this.setState({
|
this.setState({
|
||||||
modalTitle: title,
|
modalTitle: title,
|
||||||
@ -357,9 +334,6 @@ class AccessGateway extends Component {
|
|||||||
<Button type="link" size='small'
|
<Button type="link" size='small'
|
||||||
onClick={() => this.update(record['id'])}>编辑</Button>
|
onClick={() => this.update(record['id'])}>编辑</Button>
|
||||||
|
|
||||||
<Button type="link" size='small' loading={this.state.items[index]['reconnectLoading']}
|
|
||||||
onClick={() => this.reconnect(record['id'], index)}>重连</Button>
|
|
||||||
|
|
||||||
<Button type="text" size='small' danger
|
<Button type="text" size='small' danger
|
||||||
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
||||||
|
|
||||||
|
@ -148,7 +148,15 @@ class Job extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
showModal(title, obj = null) {
|
showModal = async (title, obj = null) => {
|
||||||
|
if (obj['id']) {
|
||||||
|
let result = await request.get(`/jobs/${obj['id']}`);
|
||||||
|
if (result.code !== 1) {
|
||||||
|
message.error(result.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
obj = result.data;
|
||||||
|
}
|
||||||
if (obj['func'] === 'shell-job') {
|
if (obj['func'] === 'shell-job') {
|
||||||
obj['shell'] = JSON.parse(obj['metadata'])['shell'];
|
obj['shell'] = JSON.parse(obj['metadata'])['shell'];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user