Browse Source

feat(server): Add Rustdesk Command

And add build full s6 image for rustdesk command
lejianwen 1 year ago
parent
commit
d1fc0036cb

+ 52 - 0
.github/workflows/build.yml

@@ -292,6 +292,21 @@ jobs:
292
             ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
292
             ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
293
           labels: ${{ steps.meta.outputs.labels }}
293
           labels: ${{ steps.meta.outputs.labels }}
294
 
294
 
295
+      - name: Build and push Docker Full S6 image to Docker Hub ${{ matrix.job.platform }}
296
+        if: ${{ env.SKIP_DOCKER_HUB == 'false' }}  # Only run this step if SKIP_DOCKER_HUB is false
297
+        uses: docker/build-push-action@v5
298
+        with:
299
+          context: "."
300
+          file: ./Dockerfile_full_s6
301
+          platforms: ${{ matrix.job.docker_platform }}
302
+          push: true
303
+          provenance: false
304
+          build-args: |
305
+            BUILDARCH=${{ matrix.job.platform }}
306
+          tags: |
307
+            ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-${{ matrix.job.platform }}
308
+          labels: ${{ steps.meta.outputs.labels }}
309
+
295
       - name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
310
       - name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
296
         if: ${{ env.SKIP_GHCR == 'false' }}  # Only run this step if SKIP_GHCR is false
311
         if: ${{ env.SKIP_GHCR == 'false' }}  # Only run this step if SKIP_GHCR is false
297
         uses: docker/build-push-action@v5
312
         uses: docker/build-push-action@v5
@@ -308,6 +323,21 @@ jobs:
308
             ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
323
             ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
309
           labels: ${{ steps.meta.outputs.labels }}
324
           labels: ${{ steps.meta.outputs.labels }}
310
 
325
 
326
+      - name: Build and push Docker Full S6 image to GHCR ${{ matrix.job.platform }}
327
+        if: ${{ env.SKIP_GHCR == 'false' }}  # Only run this step if SKIP_GHCR is false
328
+        uses: docker/build-push-action@v5
329
+        with:
330
+          context: "."
331
+          file: ./Dockerfile
332
+          platforms: ${{ matrix.job.docker_platform }}
333
+          push: true
334
+          provenance: false
335
+          build-args: |
336
+            BUILDARCH=${{ matrix.job.platform }}
337
+          tags: |
338
+            ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-${{ matrix.job.platform }}
339
+          labels: ${{ steps.meta.outputs.labels }}
340
+
311
   #
341
   #
312
   docker-manifest:
342
   docker-manifest:
313
     name: Push Docker Manifest
343
     name: Push Docker Manifest
@@ -378,4 +408,26 @@ jobs:
378
             ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-armv7l,
408
             ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-armv7l,
379
             ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64
409
             ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64
380
           push: true
410
           push: true
411
+          amend: true
412
+
413
+      - name: Create and push Full S6 manifest Docker Hub (:version)
414
+        if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
415
+        uses: Noelware/docker-manifest-action@master
416
+        with:
417
+          base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
418
+          extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
419
+            ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-armv7l,
420
+            ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-arm64
421
+          push: true
422
+          amend: true
423
+
424
+      - name: Create and push Full S6 manifest GHCR (:latest)
425
+        if: ${{ env.SKIP_GHCR == 'false' }}
426
+        uses: Noelware/docker-manifest-action@master
427
+        with:
428
+          base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
429
+          extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
430
+            ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-armv7l,
431
+            ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-arm64
432
+          push: true
381
           amend: true
433
           amend: true

+ 1 - 2
Dockerfile

@@ -2,9 +2,8 @@ FROM alpine
2
 
2
 
3
 ARG BUILDARCH
3
 ARG BUILDARCH
4
 WORKDIR /app
4
 WORKDIR /app
5
-RUN apk add --no-cache tzdata file
5
+RUN apk add --no-cache tzdata
6
 COPY ./${BUILDARCH}/release /app/
6
 COPY ./${BUILDARCH}/release /app/
7
-RUN file /app/apimain
8
 VOLUME /app/data
7
 VOLUME /app/data
9
 
8
 
10
 EXPOSE 21114
9
 EXPOSE 21114

+ 38 - 0
Dockerfile_full_s6

@@ -0,0 +1,38 @@
1
+FROM rustdesk/rustdesk-server-s6:latest as server
2
+
3
+FROM alpine
4
+
5
+ARG BUILDARCH
6
+WORKDIR /app
7
+RUN apk add --no-cache tzdata
8
+COPY ./${BUILDARCH}/release /app/
9
+
10
+COPY --from=server /init /init
11
+COPY --from=server /etc/s6-overlay /etc/s6-overlay
12
+COPY --from=server /package /package
13
+COPY --from=server /usr/bin/healthcheck.sh /usr/bin/healthcheck.sh
14
+COPY --from=server /usr/bin/hbbr /usr/bin/hbbr
15
+COPY --from=server /usr/bin/hbbs /usr/bin/hbbs
16
+COPY --from=server /usr/bin/rustdesk-utils /usr/bin/rustdesk-utils
17
+COPY --from=server /command /command
18
+
19
+RUN \
20
+  mkdir -p /etc/s6-overlay/s6-rc.d/api && \
21
+  echo -e "key-secret\nhbbs" > /etc/s6-overlay/s6-rc.d/api/dependencies && \
22
+  echo "longrun" > /etc/s6-overlay/s6-rc.d/api/type && \
23
+  echo "#!/command/with-contenv sh" > /etc/s6-overlay/s6-rc.d/api/run && \
24
+  echo "cd /app" >> /etc/s6-overlay/s6-rc.d/api/run && \
25
+  echo "./apimain" >> /etc/s6-overlay/s6-rc.d/api/run && \
26
+  touch /etc/s6-overlay/s6-rc.d/user/contents.d/api && \
27
+  echo "/package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/api || exit 1" >> /usr/bin/healthcheck.sh && \
28
+  ln -s /run /var/run
29
+
30
+ENV RELAY=relay.example.com
31
+ENV ENCRYPTED_ONLY=0
32
+
33
+VOLUME /data
34
+VOLUME /app/data
35
+
36
+EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119
37
+
38
+ENTRYPOINT ["/init"]

+ 2 - 1
cmd/apimain.go

@@ -169,7 +169,7 @@ func InitGlobal() {
169
 	global.Lock = lock.NewLocal()
169
 	global.Lock = lock.NewLocal()
170
 }
170
 }
171
 func DatabaseAutoUpdate() {
171
 func DatabaseAutoUpdate() {
172
-	version := 247
172
+	version := 251
173
 
173
 
174
 	db := global.DB
174
 	db := global.DB
175
 
175
 
@@ -253,6 +253,7 @@ func Migrate(version uint) {
253
 		&model.AuditFile{},
253
 		&model.AuditFile{},
254
 		&model.AddressBookCollection{},
254
 		&model.AddressBookCollection{},
255
 		&model.AddressBookCollectionRule{},
255
 		&model.AddressBookCollectionRule{},
256
+		&model.ServerCmd{},
256
 	)
257
 	)
257
 	if err != nil {
258
 	if err != nil {
258
 		fmt.Println("migrate err :=>", err)
259
 		fmt.Println("migrate err :=>", err)

+ 101 - 32
http/controller/admin/rustdesk.go

@@ -2,45 +2,114 @@ package admin
2
 
2
 
3
 import (
3
 import (
4
 	"Gwen/global"
4
 	"Gwen/global"
5
+	"Gwen/http/request/admin"
5
 	"Gwen/http/response"
6
 	"Gwen/http/response"
7
+	"Gwen/model"
8
+	"Gwen/service"
6
 	"github.com/gin-gonic/gin"
9
 	"github.com/gin-gonic/gin"
7
 )
10
 )
8
 
11
 
9
 type Rustdesk struct {
12
 type Rustdesk struct {
10
 }
13
 }
11
 
14
 
12
-// ServerConfig RUSTDESK服务配置
13
-// @Tags ADMIN
14
-// @Summary RUSTDESK服务配置
15
-// @Description 服务配置,给webclient提供api-server
16
-// @Accept  json
17
-// @Produce  json
18
-// @Success 200 {object} response.Response
19
-// @Failure 500 {object} response.Response
20
-// @Router /admin/server-config [get]
21
-// @Security token
22
-func (r *Rustdesk) ServerConfig(c *gin.Context) {
23
-	cf := &response.ServerConfigResponse{
24
-		IdServer:    global.Config.Rustdesk.IdServer,
25
-		Key:         global.Config.Rustdesk.Key,
26
-		RelayServer: global.Config.Rustdesk.RelayServer,
27
-		ApiServer:   global.Config.Rustdesk.ApiServer,
28
-	}
29
-	response.Success(c, cf)
15
+type RustdeskCmd struct {
16
+	Cmd    string `json:"cmd"`
17
+	Option string `json:"option"`
30
 }
18
 }
31
 
19
 
32
-// AppConfig APP服务配置
33
-// @Tags ADMIN
34
-// @Summary APP服务配置
35
-// @Description APP服务配置
36
-// @Accept  json
37
-// @Produce  json
38
-// @Success 200 {object} response.Response
39
-// @Failure 500 {object} response.Response
40
-// @Router /admin/app-config [get]
41
-// @Security token
42
-func (r *Rustdesk) AppConfig(c *gin.Context) {
43
-	response.Success(c, &gin.H{
44
-		"web_client": global.Config.App.WebClient,
45
-	})
20
+func (r *Rustdesk) CmdList(c *gin.Context) {
21
+	q := &admin.PageQuery{}
22
+	if err := c.ShouldBindQuery(q); err != nil {
23
+		response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
24
+		return
25
+	}
26
+	res := service.AllService.ServerCmdService.List(q.Page, 9999)
27
+	//在列表前添加系统命令
28
+	list := make([]*model.ServerCmd, 0)
29
+	list = append(list, model.SysServerCmds...)
30
+	list = append(list, res.ServerCmds...)
31
+	res.ServerCmds = list
32
+	response.Success(c, res)
33
+}
34
+
35
+func (r *Rustdesk) CmdDelete(c *gin.Context) {
36
+	f := &model.ServerCmd{}
37
+	if err := c.ShouldBindJSON(f); err != nil {
38
+		response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
39
+		return
40
+	}
41
+	if f.Id == 0 {
42
+		response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
43
+		return
44
+	}
45
+
46
+	ex := service.AllService.ServerCmdService.Info(f.Id)
47
+	if ex.Id == 0 {
48
+		response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
49
+		return
50
+	}
51
+
52
+	err := service.AllService.ServerCmdService.Delete(ex)
53
+	if err != nil {
54
+		response.Fail(c, 101, err.Error())
55
+		return
56
+	}
57
+	response.Success(c, nil)
58
+}
59
+func (r *Rustdesk) CmdCreate(c *gin.Context) {
60
+	f := &model.ServerCmd{}
61
+	if err := c.ShouldBindJSON(f); err != nil {
62
+		response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
63
+		return
64
+	}
65
+	errList := global.Validator.ValidStruct(c, f)
66
+	if len(errList) > 0 {
67
+		response.Fail(c, 101, errList[0])
68
+		return
69
+	}
70
+	err := service.AllService.ServerCmdService.Create(f)
71
+	if err != nil {
72
+		response.Fail(c, 101, err.Error())
73
+		return
74
+	}
75
+	response.Success(c, nil)
76
+}
77
+
78
+func (r *Rustdesk) CmdUpdate(c *gin.Context) {
79
+	f := &model.ServerCmd{}
80
+	if err := c.ShouldBindJSON(f); err != nil {
81
+		response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
82
+		return
83
+	}
84
+	errList := global.Validator.ValidStruct(c, f)
85
+	if len(errList) > 0 {
86
+		response.Fail(c, 101, errList[0])
87
+		return
88
+	}
89
+	ex := service.AllService.ServerCmdService.Info(f.Id)
90
+	if ex.Id == 0 {
91
+		response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
92
+		return
93
+	}
94
+	err := service.AllService.ServerCmdService.Update(f)
95
+	if err != nil {
96
+		response.Fail(c, 101, err.Error())
97
+		return
98
+	}
99
+	response.Success(c, nil)
100
+}
101
+
102
+func (r *Rustdesk) SendCmd(c *gin.Context) {
103
+	rc := &RustdeskCmd{}
104
+	c.ShouldBindJSON(rc)
105
+	if rc.Cmd == "" {
106
+		response.Fail(c, 101, "cmd is required")
107
+		return
108
+	}
109
+	res, err := service.AllService.ServerCmdService.SendCmd(rc.Cmd, rc.Option)
110
+	if err != nil {
111
+		response.Fail(c, 101, err.Error())
112
+		return
113
+	}
114
+	response.Success(c, res)
46
 }
115
 }

+ 11 - 0
http/router/admin.go

@@ -46,9 +46,20 @@ func Init(g *gin.Engine) {
46
 	ShareRecordBind(adg)
46
 	ShareRecordBind(adg)
47
 	MyBind(adg)
47
 	MyBind(adg)
48
 
48
 
49
+	RustdeskCmdBind(adg)
50
+
49
 	//访问静态文件
51
 	//访问静态文件
50
 	//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
52
 	//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
51
 }
53
 }
54
+
55
+func RustdeskCmdBind(adg *gin.RouterGroup) {
56
+	cont := &admin.Rustdesk{}
57
+	rg := adg.Group("/rustdesk")
58
+	rg.POST("/sendCmd", cont.SendCmd)
59
+	rg.GET("/cmdList", cont.CmdList)
60
+	rg.POST("/cmdDelete", cont.CmdDelete)
61
+	rg.POST("/cmdCreate", cont.CmdCreate)
62
+}
52
 func LoginBind(rg *gin.RouterGroup) {
63
 func LoginBind(rg *gin.RouterGroup) {
53
 	cont := &admin.Login{}
64
 	cont := &admin.Login{}
54
 	rg.POST("/login", cont.Login)
65
 	rg.POST("/login", cont.Login)

+ 24 - 0
model/serverCmd.go

@@ -0,0 +1,24 @@
1
+package model
2
+
3
+type ServerCmd struct {
4
+	IdModel
5
+	Cmd     string `json:"cmd" gorm:"default:'';not null;"`
6
+	Alias   string `json:"alias" gorm:"default:'';not null;"`
7
+	Option  string `json:"option" gorm:"default:'';not null;"`
8
+	Explain string `json:"explain" gorm:"default:'';not null;"`
9
+	TimeModel
10
+}
11
+
12
+type ServerCmdList struct {
13
+	ServerCmds []*ServerCmd `json:"list"`
14
+	Pagination
15
+}
16
+
17
+var SysServerCmds = []*ServerCmd{
18
+	{Cmd: "h", Option: "", Explain: "show help"},
19
+	{Cmd: "relay-servers", Alias: "rs", Option: "<separated by ,>", Explain: "set or show relay servers"},
20
+	{Cmd: "ip-blocker", Alias: "ib", Option: "[<ip>|<number>] [-]", Explain: "block or unblock ip or show blocked ip"},
21
+	{Cmd: "ip-changes", Alias: "ic", Option: "[<id>|<number>] [-]", Explain: "ip-changes(ic) [<id>|<number>] [-]"},
22
+	{Cmd: "always-use-relay(aur)", Alias: "aur", Option: "[y|n]", Explain: "always use relay"},
23
+	{Cmd: "test-geo", Alias: "tg", Option: "<ip1> <ip2>", Explain: "test geo"},
24
+}

+ 92 - 0
service/serverCmd.go

@@ -0,0 +1,92 @@
1
+package service
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/model"
6
+	"fmt"
7
+	"net"
8
+	"time"
9
+)
10
+
11
+type ServerCmdService struct{}
12
+
13
+// List
14
+func (is *ServerCmdService) List(page, pageSize uint) (res *model.ServerCmdList) {
15
+	res = &model.ServerCmdList{}
16
+	res.Page = int64(page)
17
+	res.PageSize = int64(pageSize)
18
+	tx := global.DB.Model(&model.ServerCmd{})
19
+	tx.Count(&res.Total)
20
+	tx.Scopes(Paginate(page, pageSize))
21
+	tx.Find(&res.ServerCmds)
22
+	return
23
+}
24
+
25
+// Info
26
+func (is *ServerCmdService) Info(id uint) *model.ServerCmd {
27
+	u := &model.ServerCmd{}
28
+	global.DB.Where("id = ?", id).First(u)
29
+	return u
30
+}
31
+
32
+// Delete
33
+func (is *ServerCmdService) Delete(u *model.ServerCmd) error {
34
+	return global.DB.Delete(u).Error
35
+}
36
+
37
+// Create
38
+func (is *ServerCmdService) Create(u *model.ServerCmd) error {
39
+	res := global.DB.Create(u).Error
40
+	return res
41
+}
42
+
43
+// SendCmd 发送命令
44
+func (is *ServerCmdService) SendCmd(cmd string, arg string) (string, error) {
45
+	//组装命令
46
+	cmd = cmd + " " + arg
47
+	res, err := is.SendSocketCmd("v6", cmd)
48
+	if err == nil {
49
+		return res, nil
50
+	}
51
+	//v6连接失败,尝试v4
52
+	res, err = is.SendSocketCmd("v4", cmd)
53
+	if err == nil {
54
+		return res, nil
55
+	}
56
+	return "", err
57
+}
58
+
59
+// SendSocketCmd
60
+func (is *ServerCmdService) SendSocketCmd(ty string, cmd string) (string, error) {
61
+	addr := "[::1]"
62
+	tcp := "tcp6"
63
+	if ty == "v4" {
64
+		tcp = "tcp"
65
+		addr = "127.0.0.1"
66
+	}
67
+	conn, err := net.Dial(tcp, addr+":21115")
68
+	if err != nil {
69
+		fmt.Printf("connect to id %s server failed: %v\n", ty, err)
70
+		return "", err
71
+	}
72
+	defer conn.Close()
73
+	//发送命令
74
+	_, err = conn.Write([]byte(cmd))
75
+	if err != nil {
76
+		fmt.Printf("send cmd failed: %v\n", err)
77
+		return "", err
78
+	}
79
+	time.Sleep(100 * time.Millisecond)
80
+	//读取返回
81
+	buf := make([]byte, 1024)
82
+	n, err := conn.Read(buf)
83
+	if err != nil && err.Error() != "EOF" {
84
+		fmt.Printf("read response failed: %v\n", err)
85
+		return "", err
86
+	}
87
+	return string(buf[:n]), nil
88
+}
89
+
90
+func (is *ServerCmdService) Update(f *model.ServerCmd) error {
91
+	return global.DB.Model(f).Updates(f).Error
92
+}

+ 1 - 0
service/service.go

@@ -17,6 +17,7 @@ type Service struct {
17
 	*LoginLogService
17
 	*LoginLogService
18
 	*AuditService
18
 	*AuditService
19
 	*ShareRecordService
19
 	*ShareRecordService
20
+	*ServerCmdService
20
 }
21
 }
21
 
22
 
22
 func New() *Service {
23
 func New() *Service {