Compare commits

...

14 Commits

Author SHA1 Message Date
lejianwen
a951b982b3 fix: Jwt 2025-01-15 20:26:26 +08:00
lejianwen
a33be66504 docs: Up readme 2025-01-15 20:09:08 +08:00
lejianwen
f41b9d5887 feat!: Add JWT
- `RUSTDESK_API_JWT_KEY`如果设置,将会启用JWT,token自动续期功能将失效
- 此功能是为了server端校验token的合法性
2025-01-15 19:25:28 +08:00
lejianwen
3c608463e6 docs: Up readme 2025-01-12 23:12:00 +08:00
lejianwen
eeffbe124a docs: Up readme 2025-01-12 21:35:34 +08:00
lejianwen
d7f2d54faa feat(server): Add Rustdesk Relay Server Commands 2025-01-04 20:49:44 +08:00
lejianwen
7db4b03634 style(server): fmt print to log 2025-01-02 21:49:37 +08:00
lejianwen
77760a681a docs: Up readme 2025-01-02 17:03:07 +08:00
lejianwen
f9c1447ceb fix: Fix Dockerfile_full_s6 2024-12-31 23:33:17 +08:00
lejianwen
fb749c1902 fix(server): Fix Rustdesk Sys Command 2024-12-31 23:29:05 +08:00
lejianwen
240c44aa07 feat(server): Add Rustdesk Command
And add build full s6 image for rustdesk command
2024-12-31 23:16:15 +08:00
lejianwen
92cd8642c8 docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:36:57 +08:00
lejianwen
89d90cf919 docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:35:06 +08:00
lejianwen
920c6b6d8b docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:33:11 +08:00
24 changed files with 586 additions and 193 deletions

View File

@@ -292,6 +292,21 @@ jobs:
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }} ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker Full S6 image to Docker Hub ${{ matrix.job.platform }}
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile_full_s6
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker image to GHCR ${{ matrix.job.platform }} - name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@@ -308,6 +323,21 @@ jobs:
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }} ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker Full S6 image to GHCR ${{ matrix.job.platform }}
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
# #
docker-manifest: docker-manifest:
name: Push Docker Manifest name: Push Docker Manifest
@@ -378,4 +408,26 @@ jobs:
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-armv7l, ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-armv7l,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64 ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64
push: true push: true
amend: true
- name: Create and push Full S6 manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-armv7l,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-arm64
push: true
amend: true
- name: Create and push Full S6 manifest GHCR (:latest)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-armv7l,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-arm64
push: true
amend: true amend: true

View File

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

38
Dockerfile_full_s6 Normal file
View File

@@ -0,0 +1,38 @@
FROM rustdesk/rustdesk-server-s6:latest AS server
FROM alpine
ARG BUILDARCH
WORKDIR /app
RUN apk add --no-cache tzdata
COPY ./${BUILDARCH}/release /app/
COPY --from=server /init /init
COPY --from=server /etc/s6-overlay /etc/s6-overlay
COPY --from=server /package /package
COPY --from=server /usr/bin/healthcheck.sh /usr/bin/healthcheck.sh
COPY --from=server /usr/bin/hbbr /usr/bin/hbbr
COPY --from=server /usr/bin/hbbs /usr/bin/hbbs
COPY --from=server /usr/bin/rustdesk-utils /usr/bin/rustdesk-utils
COPY --from=server /command /command
RUN \
mkdir -p /etc/s6-overlay/s6-rc.d/api && \
echo -e "key-secret\nhbbs" > /etc/s6-overlay/s6-rc.d/api/dependencies && \
echo "longrun" > /etc/s6-overlay/s6-rc.d/api/type && \
echo "#!/command/with-contenv sh" > /etc/s6-overlay/s6-rc.d/api/run && \
echo "cd /app" >> /etc/s6-overlay/s6-rc.d/api/run && \
echo "./apimain" >> /etc/s6-overlay/s6-rc.d/api/run && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/api && \
echo "/package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/api || exit 1" >> /usr/bin/healthcheck.sh && \
ln -s /run /var/run
ENV RELAY=relay.example.com
ENV ENCRYPTED_ONLY=0
VOLUME /data
VOLUME /app/data
EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119
ENTRYPOINT ["/init"]

119
README.md
View File

@@ -34,6 +34,7 @@
- 快速使用web client - 快速使用web client
- i18n - i18n
- 通过 web client 分享给游客 - 通过 web client 分享给游客
- server控制(一些官方的简单的指令 [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
- Web Client - Web Client
- 自动获取API server - 自动获取API server
- 自动获取ID服务器和KEY - 自动获取ID服务器和KEY
@@ -43,51 +44,6 @@
- CLI - CLI
- 重置管理员密码 - 重置管理员密码
## 使用前准备
### [Rustdesk](https://github.com/rustdesk/rustdesk)
#### PC客户端使用的是 ***1.3.0***,经测试 ***1.2.6+*** 都可以
#### 关于PC端链接超时或者链接不上的问题以及解决方案
##### 链接不上或者超时
因为server端相对于客户端落后版本server不会响应客户端的`secure_tcp`请求,所以客户端超时。
相关代码代码位置在`https://github.com/rustdesk/rustdesk/blob/master/src/client.rs#L322`
```rust
if !key.is_empty() && !token.is_empty() {
// mainly for the security of token
allow_err!(secure_tcp(&mut socket, key).await);
}
```
可看到当`key`和`token`都不为空时,会调用`secure_tcp`但是server端不会响应所以客户端超时
`secure_tcp` 代码位置在 `https://github.com/rustdesk/rustdesk/blob/master/src/common.rs#L1203`
##### 4种解决方案
1. server端指定key。
- 优点:简单
- 缺点:链接不是加密的
```bash
hbbs -r <relay-server-ip[:port]> -k <key>
hbbr -k <key>
```
比如
```bash
hbbs -r <relay-server-ip[:port]> -k abc1234567
hbbr -k abc1234567
```
2. server端使用系统生成的key或者自定义的密钥对但如果client已登录链接时容易超时或者链接不上可以退出登录后再链接就可以了webclient可以不用退出登录
- 优点:链接加密
- 缺点:操作麻烦
3. server端使用系统生成的key或者自定义的密钥对fork官方客户端的代码将`secure_tcp`修改成直接返回,然后通过`Github Actions`编译,下载编译后的客户端。
参考[官方文档](https://rustdesk.com/docs/en/dev/build/all/)
- 优点:链接加密,可以自定义客户端一些功能,编译后直接可用
- 缺点需要自己fork代码编译有点难度
4. 使用[我fork的代码](https://github.com/lejianwen/rustdesk),已经修改了`secure_tcp`,可以直接下载使用,[下载地址](https://github.com/lejianwen/rustdesk/releases)
- 优点:代码改动可查看,`Github Actions`编译,链接加密,直接下载使用
- 缺点:可能跟不上官方版本更新
***对链接加密要求不高的可以使用`1`,对链接加密要求高的可以使用`3`或`4`***
## 功能 ## 功能
### API 服务 ### API 服务
@@ -120,14 +76,14 @@
2. 普通用户界面 2. 普通用户界面
![web_user](docs/web_admin_user.png) ![web_user](docs/web_admin_user.png)
右上角可以更改密码,可以切换语言,可以切换`白天/黑夜`模式 右上角可以更改密码,可以切换语言,可以切换`白天/黑夜`模式
![web_resetpwd](docs/web_resetpwd.png) ![web_resetpwd](docs/web_resetpwd.png)
3. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组` 3. 每个用户可以多个地址簿,也可以将地址簿共享给其他用户
![web_admin_gr](docs/web_admin_gr.png) 4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组``普通组`
4. 可以直接打开webclient方便使用也可以分享给游客游客可以直接通过webclient远程到设备 5. 可以直接打开webclient方便使用也可以分享给游客游客可以直接通过webclient远程到设备
![web_webclient](docs/admin_webclient.png) ![web_webclient](docs/admin_webclient.png)
5. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台 6. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台
![web_admin_oauth](docs/web_admin_oauth.png) ![web_admin_oauth](docs/web_admin_oauth.png)
- 对于`Google``Github`, `Issuer``Scopes`不需要填写. - 对于`Google``Github`, `Issuer``Scopes`不需要填写.
- 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email``preferred_username` - 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email``preferred_username`
@@ -135,6 +91,21 @@
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers) 中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
- `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback` - `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback`
,比如`http://127.0.0.1:21114/api/oauth/callback` ,比如`http://127.0.0.1:21114/api/oauth/callback`
7. 登录日志
8. 链接日志
9. 文件传输日志
10. server控制
- `简易模式`,已经界面化了一些简单的指令,可以直接在后台执行
![rustdesk_command_simple](./docs/rustdesk_command_simple.png)
- `高级模式`,直接在后台执行指令
* 可以官方指令
* 可以添加自定义指令
* 可以执行自定义指令
![rustdesk_command_advance](./docs/rustdesk_command_advance.png)
### Web Client: ### Web Client:
@@ -205,6 +176,9 @@ logger:
proxy: proxy:
enable: false enable: false
host: "" host: ""
jwt:
key: ""
expire-duration: 360000
``` ```
### 环境变量 ### 环境变量
@@ -243,6 +217,9 @@ proxy:
| ----PROXY配置----- | --------------- | ---------- | | ----PROXY配置----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` | | RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` | | RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
| ----JWT配置---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | 360000 |
### 运行 ### 运行
@@ -308,7 +285,49 @@ proxy:
6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。 6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。
#### 使用我fork后的server-s6镜像运行
- github https://github.com/lejianwen/rustdesk-server
- docker hub https://hub.docker.com/r/lejianwen/rustdesk-server-s6
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk:
ports:
- 21114:21114
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: lejianwen/rustdesk-server-s6:latest
environment:
- RELAY=<relay_server[:port]>
- ENCRYPTED_ONLY=1
- MUST_LOGIN=N
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
volumes:
- /data/rustdesk/server:/data
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
## 其他 ## 其他
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- [链接超时问题](https://github.com/lejianwen/rustdesk-api/issues/92)
- [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer) - [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [webclient来源](https://hub.docker.com/r/keyurbhole/flutter_web_desk) - [webclient来源](https://hub.docker.com/r/keyurbhole/flutter_web_desk)

View File

@@ -33,6 +33,7 @@ desktop software that provides self-hosted solutions.
- Quick access to web client - Quick access to web client
- i18n - i18n
- Share to guest by web client - Share to guest by web client
- Server control (some simple official commands [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
- Web Client - Web Client
- Automatically obtain API server - Automatically obtain API server
- Automatically obtain ID server and KEY - Automatically obtain ID server and KEY
@@ -40,52 +41,6 @@ desktop software that provides self-hosted solutions.
- Visitors are remotely to the device via a temporary sharing link - Visitors are remotely to the device via a temporary sharing link
- CLI - CLI
- Reset admin password - Reset admin password
## Prerequisites
### [Rustdesk](https://github.com/rustdesk/rustdesk)
#### The PC client uses version ***1.3.0***, and versions ***1.2.6+*** have been tested to work.
#### Solutions for PC client connection timeout or connection issues
##### Connection issues or timeouts
Because the server version lags behind the client version, the server does not respond to the client's `secure_tcp` request, causing the client to timeout.
Relevant code can be found at `https://github.com/rustdesk/rustdesk/blob/master/src/client.rs#L322`
```rust
if !key.is_empty() && !token.is_empty() {
// mainly for the security of token
allow_err!(secure_tcp(&mut socket, key).await);
}
```
As seen, when both `key` and `token` are not empty, `secure_tcp` is called, but the server does not respond, causing the client to timeout.
The `secure_tcp` code is located at `https://github.com/rustdesk/rustdesk/blob/master/src/common.rs#L1203`
##### Four Solutions
1. Specify the key on the server.
- Advantage: Simple
- Disadvantage: The connection is not encrypted
```bash
hbbs -r <relay-server-ip[:port]> -k <key>
hbbr -k <key>
```
For example
```bash
hbbs -r <relay-server-ip[:port]> -k abc1234567
hbbr -k abc1234567
```
2. Use a system-generated key or a custom key pair on the server. If the client is already logged in, it may timeout or fail to connect. Logging out and reconnecting usually resolves the issue, and the web client does not need to log out.
- Advantage: Encrypted connection
- Disadvantage: Complicated operation
3. Use a system-generated key or a custom key pair on the server, fork the official client code to modify `secure_tcp` to return directly, then compile using `Github Actions` and download the compiled client.
Refer to [official documentation](https://rustdesk.com/docs/en/dev/build/all/)
- Advantage: Encrypted connection, customizable client features, ready to use after compilation
- Disadvantage: Requires forking code and compiling, which can be challenging
4. Use [my forked code](https://github.com/lejianwen/rustdesk), which has already modified `secure_tcp`. You can download and use it directly from [here](https://github.com/lejianwen/rustdesk/releases)
- Advantage: Code changes are viewable, compiled with `Github Actions`, encrypted connection, ready to use
- Disadvantage: May not keep up with official version updates
***If encryption is not a high priority, use `1`. If encryption is important, use `3` or `4`.***
## Overview ## Overview
@@ -124,12 +79,11 @@ installation are `admin` `admin`, please change the password immediately.
In the top right corner, you can change the password, switch languages, and toggle between `day/night` mode. In the top right corner, you can change the password, switch languages, and toggle between `day/night` mode.
![web_resetpwd](docs/en_img/web_resetpwd.png) ![web_resetpwd](docs/en_img/web_resetpwd.png)
3. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`. 3. Each user can have multiple address books, which can also be shared with other users.
![web_admin_gr](docs/en_img/web_admin_gr.png) 4. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`.
4. You can directly launch the client or open the web client for convenience; you can also share it with guests, who can remotely access the device via the web client. 5. You can directly launch the client or open the web client for convenience; you can also share it with guests, who can remotely access the device via the web client.
![web_webclient](docs/en_img/admin_webclient.png) ![web_webclient](docs/en_img/admin_webclient.png)
5. OAuth support: Currently, `GitHub`, `Google` and `OIDC` are supported. You need to create an `OAuth App` and configure it in 6. OAuth support: Currently, `GitHub`, `Google` and `OIDC` are supported. You need to create an `OAuth App` and configure it in
the admin panel. the admin panel.
![web_admin_oauth](docs/en_img/web_admin_oauth.png) ![web_admin_oauth](docs/en_img/web_admin_oauth.png)
- For `Google` and `Github`, you don't need to fill the `Issuer` and `Scpoes` - For `Google` and `Github`, you don't need to fill the `Issuer` and `Scpoes`
@@ -138,6 +92,23 @@ installation are `admin` `admin`, please change the password immediately.
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers). at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oauth/callback`, - Set the `Authorization callback URL` to `http://<your server[:port]>/api/oauth/callback`,
e.g., `http://127.0.0.1:21114/api/oauth/callback`. e.g., `http://127.0.0.1:21114/api/oauth/callback`.
7. Login logs
8. Connection logs
9. File transfer logs
10. Server control
- `Simple mode`, some simple commands have been GUI-ized and can be executed directly in the backend
![rustdesk_command_simple](./docs/en_img/rustdesk_command_simple.png)
- `Advanced mode`, commands can be executed directly in the backend
* Official commands can be used
* Custom commands can be added
* Custom commands can be executed
![rustdesk_command_advance](./docs/en_img/rustdesk_command_advance.png)
### Web Client: ### Web Client:
@@ -208,44 +179,50 @@ logger:
proxy: proxy:
enable: false enable: false
host: "" host: ""
jwt:
key: ""
expire-duration: 360000
``` ```
### Environment Variables ### Environment Variables
The prefix for variable names is `RUSTDESK_API`. If environment variables exist, they will override the configurations in the configuration file. The prefix for variable names is `RUSTDESK_API`. If environment variables exist, they will override the configurations in the configuration file.
| Variable Name | Description | Example | | Variable Name | Description | Example |
|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------| |---------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------|
| TZ | timezone | Asia/Shanghai | | TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` | | RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 | | RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 |
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` | | RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` | | RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` |
| ----- ADMIN Configuration----- | ---------- | ---------- | | ----- ADMIN Configuration----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` | | RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | | | RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` | | RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- | | ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 | | RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- | | ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite | | RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 | | RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 | | RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 | | RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- | | ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root | | RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 | | RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 | | RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk | | RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- | | ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 | | RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 | | RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 | | RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 | | RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` | | RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` | | RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` |
| ---- PROXY ----- | --------------- | ---------- | | ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` | | RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` | | RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
| ----JWT---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | JWT KEY. Set empty to disable jwt | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT expire duration | 360000 |
### Installation Steps ### Installation Steps
@@ -314,8 +291,47 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
6. Open your browser and visit `http://<your server[:port]>/_admin/`, with default credentials `admin admin`. Please 6. Open your browser and visit `http://<your server[:port]>/_admin/`, with default credentials `admin admin`. Please
change the password promptly. change the password promptly.
#### Running with my forked server-s6 image
- github https://github.com/lejianwen/rustdesk-server
- docker hub https://hub.docker.com/r/lejianwen/rustdesk-server-s6
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk:
ports:
- 21114:21114
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: lejianwen/rustdesk-server-s6:latest
environment:
- RELAY=<relay_server[:port]>
- ENCRYPTED_ONLY=1
- MUST_LOGIN=N
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
volumes:
- /data/rustdesk/server:/data
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
## Others ## Others
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- [Connection Timeout](https://github.com/lejianwen/rustdesk-api/issues/92)
- [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer) - [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [Web client source](https://hub.docker.com/r/keyurbhole/flutter_web_desk) - [Web client source](https://hub.docker.com/r/keyurbhole/flutter_web_desk)

View File

@@ -5,6 +5,7 @@ import (
"Gwen/global" "Gwen/global"
"Gwen/http" "Gwen/http"
"Gwen/lib/cache" "Gwen/lib/cache"
"Gwen/lib/jwt"
"Gwen/lib/lock" "Gwen/lib/lock"
"Gwen/lib/logger" "Gwen/lib/logger"
"Gwen/lib/orm" "Gwen/lib/orm"
@@ -17,6 +18,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
"strconv" "strconv"
"time"
) )
// @title 管理系统API // @title 管理系统API
@@ -163,13 +165,13 @@ func InitGlobal() {
//jwt //jwt
//fmt.Println(global.Config.Jwt.PrivateKey) //fmt.Println(global.Config.Jwt.PrivateKey)
//global.Jwt = jwt.NewJwt(global.Config.Jwt.PrivateKey, global.Config.Jwt.ExpireDuration*time.Second) global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration*time.Second)
//locker //locker
global.Lock = lock.NewLocal() global.Lock = lock.NewLocal()
} }
func DatabaseAutoUpdate() { func DatabaseAutoUpdate() {
version := 247 version := 260
db := global.DB db := global.DB
@@ -253,6 +255,7 @@ func Migrate(version uint) {
&model.AuditFile{}, &model.AuditFile{},
&model.AddressBookCollection{}, &model.AddressBookCollection{},
&model.AddressBookCollectionRule{}, &model.AddressBookCollectionRule{},
&model.ServerCmd{},
) )
if err != nil { if err != nil {
fmt.Println("migrate err :=>", err) fmt.Println("migrate err :=>", err)

View File

@@ -26,16 +26,19 @@ rustdesk:
relay-server: "192.168.1.66:21117" relay-server: "192.168.1.66:21117"
api-server: "http://127.0.0.1:21114" api-server: "http://127.0.0.1:21114"
key: "" key: ""
key-file: "./conf/data/id_ed25519.pub" key-file: "/data/id_ed25519.pub"
personal: 1 personal: 1
webclient-magic-queryonline: 0 webclient-magic-queryonline: 0
logger: logger:
path: "./runtime/log.txt" path: "./runtime/log.txt"
level: "warn" #trace,debug,info,warn,error,fatal level: "debug" #trace,debug,info,warn,error,fatal
report-caller: true report-caller: true
proxy: proxy:
enable: false enable: false
host: "http://127.0.0.1:1080" host: "http://127.0.0.1:1080"
jwt:
key: ""
expire-duration: 360000
redis: redis:
addr: "127.0.0.1:6379" addr: "127.0.0.1:6379"
password: "" password: ""
@@ -53,6 +56,4 @@ oss:
callback-url: "" callback-url: ""
expire-time: 30 expire-time: 30
max-byte: 10240 max-byte: 10240
jwt:
private-key: "./conf/jwt_pri.pem"
expire-duration: 360000

View File

View File

@@ -3,6 +3,6 @@ package config
import "time" import "time"
type Jwt struct { type Jwt struct {
PrivateKey string `mapstructure:"private-key"` Key string `mapstructure:"key"`
ExpireDuration time.Duration `mapstructure:"expire-duration"` ExpireDuration time.Duration `mapstructure:"expire-duration"`
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -2,45 +2,127 @@ package admin
import ( import (
"Gwen/global" "Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response" "Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type Rustdesk struct { type Rustdesk struct {
} }
// ServerConfig RUSTDESK服务配置 type RustdeskCmd struct {
// @Tags ADMIN Cmd string `json:"cmd"`
// @Summary RUSTDESK服务配置 Option string `json:"option"`
// @Description 服务配置,给webclient提供api-server Target string `json:"target"`
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/server-config [get]
// @Security token
func (r *Rustdesk) ServerConfig(c *gin.Context) {
cf := &response.ServerConfigResponse{
IdServer: global.Config.Rustdesk.IdServer,
Key: global.Config.Rustdesk.Key,
RelayServer: global.Config.Rustdesk.RelayServer,
ApiServer: global.Config.Rustdesk.ApiServer,
}
response.Success(c, cf)
} }
// AppConfig APP服务配置 func (r *Rustdesk) CmdList(c *gin.Context) {
// @Tags ADMIN q := &admin.PageQuery{}
// @Summary APP服务配置 if err := c.ShouldBindQuery(q); err != nil {
// @Description APP服务配置 response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
// @Accept json return
// @Produce json }
// @Success 200 {object} response.Response res := service.AllService.ServerCmdService.List(q.Page, 9999)
// @Failure 500 {object} response.Response //在列表前添加系统命令
// @Router /admin/app-config [get] list := make([]*model.ServerCmd, 0)
// @Security token list = append(list, model.SysIdServerCmds...)
func (r *Rustdesk) AppConfig(c *gin.Context) { list = append(list, model.SysRelayServerCmds...)
response.Success(c, &gin.H{ list = append(list, res.ServerCmds...)
"web_client": global.Config.App.WebClient, res.ServerCmds = list
}) response.Success(c, res)
}
func (r *Rustdesk) CmdDelete(c *gin.Context) {
f := &model.ServerCmd{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
ex := service.AllService.ServerCmdService.Info(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ServerCmdService.Delete(ex)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) CmdCreate(c *gin.Context) {
f := &model.ServerCmd{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
err := service.AllService.ServerCmdService.Create(f)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) CmdUpdate(c *gin.Context) {
f := &model.ServerCmd{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.ServerCmdService.Info(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ServerCmdService.Update(f)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) SendCmd(c *gin.Context) {
rc := &RustdeskCmd{}
if err := c.ShouldBindJSON(rc); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if rc.Cmd == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if rc.Target == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if rc.Target != model.ServerCmdTargetIdServer && rc.Target != model.ServerCmdTargetRelayServer {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
res, err := service.AllService.ServerCmdService.SendCmd(rc.Target, rc.Cmd, rc.Option)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, res)
} }

View File

@@ -1,6 +1,7 @@
package middleware package middleware
import ( import (
"Gwen/global"
"Gwen/service" "Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -27,7 +28,21 @@ func RustAuth() gin.HandlerFunc {
//提取token格式是Bearer {token} //提取token格式是Bearer {token}
//这里只是简单的提取 //这里只是简单的提取
token = token[7:] token = token[7:]
//验证token //验证token
//检查是否设置了jwt key
if len(global.Jwt.Key) > 0 {
uid, _ := service.AllService.UserService.VerifyJWT(token)
if uid == 0 {
c.JSON(401, gin.H{
"error": "Unauthorized",
})
c.Abort()
return
}
}
user, ut := service.AllService.UserService.InfoByAccessToken(token) user, ut := service.AllService.UserService.InfoByAccessToken(token)
if user.Id == 0 { if user.Id == 0 {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
@@ -38,7 +53,7 @@ func RustAuth() gin.HandlerFunc {
} }
if !service.AllService.UserService.CheckUserEnable(user) { if !service.AllService.UserService.CheckUserEnable(user) {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
"error": "账号已被禁用", "error": "Unauthorized",
}) })
c.Abort() c.Abort()
return return

View File

@@ -46,9 +46,20 @@ func Init(g *gin.Engine) {
ShareRecordBind(adg) ShareRecordBind(adg)
MyBind(adg) MyBind(adg)
RustdeskCmdBind(adg)
//访问静态文件 //访问静态文件
//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload")) //g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
} }
func RustdeskCmdBind(adg *gin.RouterGroup) {
cont := &admin.Rustdesk{}
rg := adg.Group("/rustdesk")
rg.POST("/sendCmd", cont.SendCmd)
rg.GET("/cmdList", cont.CmdList)
rg.POST("/cmdDelete", cont.CmdDelete)
rg.POST("/cmdCreate", cont.CmdCreate)
}
func LoginBind(rg *gin.RouterGroup) { func LoginBind(rg *gin.RouterGroup) {
cont := &admin.Login{} cont := &admin.Login{}
rg.POST("/login", cont.Login) rg.POST("/login", cont.Login)

View File

@@ -1,14 +1,13 @@
package jwt package jwt
import ( import (
"crypto/rsa" "fmt"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"os"
"time" "time"
) )
type Jwt struct { type Jwt struct {
privateKey *rsa.PrivateKey Key []byte
TokenExpireDuration time.Duration TokenExpireDuration time.Duration
} }
@@ -17,31 +16,28 @@ type UserClaims struct {
jwt.RegisteredClaims jwt.RegisteredClaims
} }
func NewJwt(privateKeyFile string, tokenExpireDuration time.Duration) *Jwt { func NewJwt(key string, tokenExpireDuration time.Duration) *Jwt {
privateKeyContent, err := os.ReadFile(privateKeyFile)
if err != nil {
panic(err)
}
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyContent)
if err != nil {
panic(err)
}
return &Jwt{ return &Jwt{
privateKey: privateKey, Key: []byte(key),
TokenExpireDuration: tokenExpireDuration, TokenExpireDuration: tokenExpireDuration,
} }
} }
func (s *Jwt) GenerateToken(userId uint) string { func (s *Jwt) GenerateToken(userId uint) string {
t := jwt.NewWithClaims(jwt.SigningMethodRS256, if len(s.Key) == 0 {
fmt.Println("jwt key is nil")
return ""
}
t := jwt.NewWithClaims(jwt.SigningMethodHS256,
UserClaims{ UserClaims{
UserId: userId, UserId: userId,
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.TokenExpireDuration)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.TokenExpireDuration)),
}, },
}) })
token, err := t.SignedString(s.privateKey) token, err := t.SignedString(s.Key)
if err != nil { if err != nil {
fmt.Printf("jwt token generate error: %v", err)
return "" return ""
} }
return token return token
@@ -49,7 +45,7 @@ func (s *Jwt) GenerateToken(userId uint) string {
func (s *Jwt) ParseToken(tokenString string) (uint, error) { func (s *Jwt) ParseToken(tokenString string) (uint, error) {
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return s.privateKey.Public(), nil return s.Key, nil
}) })
if err != nil { if err != nil {
return 0, err return 0, err

61
model/serverCmd.go Normal file
View File

@@ -0,0 +1,61 @@
package model
type ServerCmd struct {
IdModel
Cmd string `json:"cmd" gorm:"default:'';not null;"`
Alias string `json:"alias" gorm:"default:'';not null;"`
Option string `json:"option" gorm:"default:'';not null;"`
Explain string `json:"explain" gorm:"default:'';not null;"`
Target string `json:"target" gorm:"default:'';not null;"`
TimeModel
}
type ServerCmdList struct {
ServerCmds []*ServerCmd `json:"list"`
Pagination
}
const (
ServerCmdTargetIdServer = "21115"
ServerCmdTargetRelayServer = "21117"
)
var SysIdServerCmds = []*ServerCmd{
{Cmd: "h", Option: "", Explain: "show help", Target: ServerCmdTargetIdServer},
{Cmd: "relay-servers", Alias: "rs", Option: "<separated by ,>", Explain: "set or show relay servers", Target: ServerCmdTargetIdServer},
{Cmd: "ip-blocker", Alias: "ib", Option: "[<ip>|<number>] [-]", Explain: "block or unblock ip or show blocked ip", Target: ServerCmdTargetIdServer},
{Cmd: "ip-changes", Alias: "ic", Option: "[<id>|<number>] [-]", Explain: "ip-changes(ic) [<id>|<number>] [-]", Target: ServerCmdTargetIdServer},
{Cmd: "always-use-relay", Alias: "aur", Option: "[y|n]", Explain: "always use relay", Target: ServerCmdTargetIdServer},
{Cmd: "test-geo", Alias: "tg", Option: "<ip1> <ip2>", Explain: "test geo", Target: ServerCmdTargetIdServer},
}
/*
"blacklist-add(ba) <ip>",
"blacklist-remove(br) <ip>",
"blacklist(b) <ip>",
"blocklist-add(Ba) <ip>",
"blocklist-remove(Br) <ip>",
"blocklist(B) <ip>",
"downgrade-threshold(dt) [value]",
"downgrade-start-check(t) [value(second)]",
"limit-speed(ls) [value(Mb/s)]",
"total-bandwidth(tb) [value(Mb/s)]",
"single-bandwidth(sb) [value(Mb/s)]",
"usage(u)"
*/
var SysRelayServerCmds = []*ServerCmd{
{Cmd: "h", Option: "", Explain: "show help", Target: ServerCmdTargetRelayServer},
{Cmd: "blacklist-add", Alias: "ba", Option: "<ip>", Explain: "blacklist-add(ba) <ip>", Target: ServerCmdTargetRelayServer},
{Cmd: "blacklist-remove", Alias: "br", Option: "<ip>", Explain: "blacklist-remove(br) <ip>", Target: ServerCmdTargetRelayServer},
{Cmd: "blacklist", Alias: "b", Option: "<ip>", Explain: "blacklist(b) <ip>", Target: ServerCmdTargetRelayServer},
{Cmd: "blocklist-add", Alias: "Ba", Option: "<ip>", Explain: "blocklist-add(Ba) <ip>", Target: ServerCmdTargetRelayServer},
{Cmd: "blocklist-remove", Alias: "Br", Option: "<ip>", Explain: "blocklist-remove(Br) <ip>", Target: ServerCmdTargetRelayServer},
{Cmd: "blocklist", Alias: "B", Option: "<ip>", Explain: "blocklist(B) <ip>", Target: ServerCmdTargetRelayServer},
{Cmd: "downgrade-threshold", Alias: "dt", Option: "[value]", Explain: "downgrade-threshold(dt) [value]", Target: ServerCmdTargetRelayServer},
{Cmd: "downgrade-start-check", Alias: "t", Option: "[value(second)]", Explain: "downgrade-start-check(t) [value(second)]", Target: ServerCmdTargetRelayServer},
{Cmd: "limit-speed", Alias: "ls", Option: "[value(Mb/s)]", Explain: "limit-speed(ls) [value(Mb/s)]", Target: ServerCmdTargetRelayServer},
{Cmd: "total-bandwidth", Alias: "tb", Option: "[value(Mb/s)]", Explain: "total-bandwidth(tb) [value(Mb/s)]", Target: ServerCmdTargetRelayServer},
{Cmd: "single-bandwidth", Alias: "sb", Option: "[value(Mb/s)]", Explain: "single-bandwidth(sb) [value(Mb/s)]", Target: ServerCmdTargetRelayServer},
{Cmd: "usage", Alias: "u", Option: "", Explain: "usage(u)", Target: ServerCmdTargetRelayServer},
}

92
service/serverCmd.go Normal file
View File

@@ -0,0 +1,92 @@
package service
import (
"Gwen/global"
"Gwen/model"
"fmt"
"net"
"time"
)
type ServerCmdService struct{}
// List
func (is *ServerCmdService) List(page, pageSize uint) (res *model.ServerCmdList) {
res = &model.ServerCmdList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.ServerCmd{})
tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize))
tx.Find(&res.ServerCmds)
return
}
// Info
func (is *ServerCmdService) Info(id uint) *model.ServerCmd {
u := &model.ServerCmd{}
global.DB.Where("id = ?", id).First(u)
return u
}
// Delete
func (is *ServerCmdService) Delete(u *model.ServerCmd) error {
return global.DB.Delete(u).Error
}
// Create
func (is *ServerCmdService) Create(u *model.ServerCmd) error {
res := global.DB.Create(u).Error
return res
}
// SendCmd 发送命令
func (is *ServerCmdService) SendCmd(port string, cmd string, arg string) (string, error) {
//组装命令
cmd = cmd + " " + arg
res, err := is.SendSocketCmd("v6", port, cmd)
if err == nil {
return res, nil
}
//v6连接失败尝试v4
res, err = is.SendSocketCmd("v4", port, cmd)
if err == nil {
return res, nil
}
return "", err
}
// SendSocketCmd
func (is *ServerCmdService) SendSocketCmd(ty string, port string, cmd string) (string, error) {
addr := "[::1]"
tcp := "tcp6"
if ty == "v4" {
tcp = "tcp"
addr = "127.0.0.1"
}
conn, err := net.Dial(tcp, fmt.Sprintf("%s:%s", addr, port))
if err != nil {
global.Logger.Debugf("%s connect to id server failed: %v", ty, err)
return "", err
}
defer conn.Close()
//发送命令
_, err = conn.Write([]byte(cmd))
if err != nil {
global.Logger.Debugf("%s send cmd failed: %v", ty, err)
return "", err
}
time.Sleep(100 * time.Millisecond)
//读取返回
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil && err.Error() != "EOF" {
global.Logger.Debugf("%s read response failed: %v", ty, err)
return "", err
}
return string(buf[:n]), nil
}
func (is *ServerCmdService) Update(f *model.ServerCmd) error {
return global.DB.Model(f).Updates(f).Error
}

View File

@@ -17,6 +17,7 @@ type Service struct {
*LoginLogService *LoginLogService
*AuditService *AuditService
*ShareRecordService *ShareRecordService
*ServerCmdService
} }
func New() *Service { func New() *Service {

View File

@@ -68,6 +68,9 @@ func (us *UserService) InfoByAccessToken(token string) (*model.User, *model.User
// GenerateToken 生成token // GenerateToken 生成token
func (us *UserService) GenerateToken(u *model.User) string { func (us *UserService) GenerateToken(u *model.User) string {
if len(global.Jwt.Key) > 0 {
return global.Jwt.GenerateToken(u.Id)
}
return utils.Md5(u.Username + time.Now().String()) return utils.Md5(u.Username + time.Now().String())
} }
@@ -461,3 +464,7 @@ func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
func (us *UserService) BatchDeleteUserToken(ids []uint) error { func (us *UserService) BatchDeleteUserToken(ids []uint) error {
return global.DB.Where("id in ?", ids).Delete(&model.UserToken{}).Error return global.DB.Where("id in ?", ids).Delete(&model.UserToken{}).Error
} }
func (us *UserService) VerifyJWT(token string) (uint, error) {
return global.Jwt.ParseToken(token)
}