Compare commits

...

29 Commits

Author SHA1 Message Date
ljw
62a22c697d up web client can get pwd if exist 2024-09-24 10:15:04 +08:00
ljw
be497a5aa7 add docker.yml 2024-09-23 14:43:43 +08:00
ljw
fc3e16bc63 up README.md 2024-09-23 10:21:43 +08:00
ljw
df35912461 up release.yml 2024-09-23 10:13:44 +08:00
ljw
0ddc66a854 up release.yml 2024-09-23 10:10:29 +08:00
ljw
716d557d66 up test.yml 2024-09-23 10:07:10 +08:00
ljw
f9edcb9d47 up test.yml 2024-09-22 21:40:17 +08:00
ljw
d4623c5bc9 up test.yml 2024-09-22 21:28:43 +08:00
ljw
02ba4a3330 up test.yml 2024-09-22 20:30:12 +08:00
ljw
a72d4eaf78 up test.yml 2024-09-22 19:46:37 +08:00
ljw
4d3abb2dc0 up test.yml 2024-09-22 17:58:42 +08:00
ad6a8d1f7a Update test.yml 2024-09-21 22:49:06 +08:00
a5c8c2ac97 Update test.yml 2024-09-21 21:55:36 +08:00
1237004cb1 Update test.yml 2024-09-21 21:44:43 +08:00
c2bcd17df7 Update go.yml 2024-09-21 21:43:31 +08:00
bb49cbdd50 Update test.yml 2024-09-21 21:40:36 +08:00
6ad770a824 Create test.yml 2024-09-21 21:35:55 +08:00
593eeb3ac3 Update go.yml 2024-09-21 21:17:03 +08:00
6a7be3ef84 Create go.yml 2024-09-21 21:13:54 +08:00
ljw
3addc12f45 bug fix 2024-09-20 20:46:32 +08:00
ljw
1e3403e3c5 add google oauth 2024-09-20 12:13:15 +08:00
ljw
ae4672174a up 2024-09-20 10:14:38 +08:00
ljw
f935fed5f7 up readme 2024-09-20 10:12:25 +08:00
ljw
989990c869 up Docker image add tzdata 2024-09-20 10:10:04 +08:00
ljw
8b8a44e46b fix 2024-09-20 09:05:15 +08:00
ljw
2ed2884118 up README_EN.md 2024-09-19 12:05:53 +08:00
ljw
03bd34cadd up README_EN.md 2024-09-19 12:03:18 +08:00
ljw
1c3f87fecb up README.md 2024-09-19 11:07:54 +08:00
ljw
d45e85d29b up README.md 2024-09-19 11:02:54 +08:00
20 changed files with 616 additions and 241 deletions

38
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Build and Push Docker Image
on:
push:
tags:
- 'v*.*.*' # 仅当推送标签(例如 v1.0.0)时触发
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Extract version from tag
id: vars
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: lejianwen/rustdesk-api:latest, lejianwen/rustdesk-api:${{ env.TAG }}

27
.github/workflows/go.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22'
- name: mod tidy
run: go mod tidy
- name: Build
run: go build -v -o release/apimain cmd/apimain.go
- name: Test
run: go test -v cmd/apimain.go

95
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Build and Release
on:
push:
tags:
- 'v*.*.*' # 当推送带有版本号的 tag例如 v1.0.0)时触发工作流
#on:
# push:
# branches: [ "master" ]
# pull_request:
# branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [ linux, windows ] # 指定要构建的操作系统
goarch: [ amd64 ] # 指定架构
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go environment
uses: actions/setup-go@v4
with:
go-version: '1.22' # 选择 Go 版本
- name: Set up npm
uses: actions/setup-node@v2
with:
node-version: '20'
- name: install gcc zip musl
run: |
if [ "${{ matrix.goos }}" = "windows" ]; then
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
else
sudo apt-get install musl musl-dev musl-tools -y
fi
- name: build rustdesk-api-web
run: |
git clone https://github.com/lejianwen/rustdesk-api-web
cd rustdesk-api-web
npm install
npm run build
mkdir ../resources/admin/ -p
cp -ar dist/* ../resources/admin/
- name: tidy
run: go mod tidy
- name: swag
run: |
go install github.com/swaggo/swag/cmd/swag@latest
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
- name: Build for ${{ matrix.goos }}-${{ matrix.goarch }}
run: |
mkdir release -p
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
mkdir -p release/data
mkdir -p release/runtime
if [ "${{ matrix.goos }}" = "windows" ]; then
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain.exe ./cmd/apimain.go
zip -r ${{ matrix.goos}}-${{ matrix.goarch }}.zip ./release
else
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
tar -czf ${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz ./release
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: myapp-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
${{ matrix.goos}}-${{ matrix.goarch }}.zip
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
${{ matrix.goos}}-${{ matrix.goarch }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -17,11 +17,7 @@ RUN set -eux; \
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin; \
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api; \
go env -w GO111MODULE=on;\
go env -w CGO_ENABLED=1;\
go env -w GOOS=linux;\
go env -w GOARCH=amd64;\
go env -w CGO_LDFLAGS="-static"; \
go build -o release/apimain cmd/apimain.go; \
CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go; \
cp -ar resources release/; \
mkdir -p release/resources/public; \
cp -ar docs release/; \
@@ -32,6 +28,7 @@ RUN set -eux; \
VOLUME /app/data
FROM alpine
WORKDIR /app
RUN apk add --no-cache tzdata
COPY --from=builder /go/rustdesk-api/release /app/
EXPOSE 21114

191
README.md
View File

@@ -9,6 +9,7 @@
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/release.yml/badge.svg"/>
</div>
## 使用前准备
@@ -18,17 +19,17 @@
1. PC客户端使用的是 ***1.3.0***,经测试 ***1.2.6+*** 都可以
2. server端必须指定key不能用自带的生成的key,否则可能链接不上或者超时
```bash
hbbs -r <relay-server-ip[:port]> -k <key>
hbbr -k <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
```
```bash
hbbs -r <relay-server-ip[:port]> -k abc1234567
hbbr -k abc1234567
```
## 功能
@@ -36,8 +37,8 @@ hbbr -k abc1234567
#### 登录
- 添加了`github`登录需要在后台配置好就可以用了具体可看后台OAuth配置
- 添加了web后台授权登录
- 添加了`github`和`google`授权登录需要在后台配置好就可以用了具体可看后台OAuth配置
- 添加了web后台授权登录,点击后直接登录后台就自动登录客户端了
![pc_login](docs/pc_login.png)
@@ -53,7 +54,7 @@ hbbr -k abc1234567
***前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
***后台访问地址是`http://<your server>:21114/_admin/`初次安装管理员为用户名密码为`admin` `admin`,请即时更改密码***
***后台访问地址是`http://<your server>[:port]/_admin/`初次安装管理员为用户名密码为`admin` `admin`,请即时更改密码***
1. 管理员界面
![web_admin](docs/web_admin.png)
@@ -66,6 +67,12 @@ hbbr -k abc1234567
![web_admin_gr](docs/web_admin_gr.png)
5. 可以直接打开webclient方便使用
![web_webclient](docs/admin_webclient.png)
6. Oauth,暂时只支持了`Github`和`Google`, 需要创建一个`OAuth App`,然后配置到后台
![web_admin_oauth](docs/web_admin_oauth.png)
- `github oauth app`在`Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
- `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback`
,比如`http://127.0.0.1:21114/api/oauth/callback`
### **Web Client**:
@@ -113,6 +120,7 @@ rustdesk:
| 变量名 | 说明 | 示例 |
|:------------------------------------|:-------------------------------------|-----------------------------|
| TZ | 时区 | Asia/Shanghai |
| -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----------GORM配置------------------ | ------------------------------------ | --------------------------- |
@@ -139,6 +147,7 @@ rustdesk:
```bash
docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \
-e TZ=Asia/Shanghai \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
@@ -148,87 +157,89 @@ lejianwen/rustdesk-api
2. 使用`docker compose`
- 简单示例
- 简单示例
```docker-compose
services:
rustdesk-api:
container_name: rustdesk-api
environment:
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
```docker-compose
services:
rustdesk-api:
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
- 根据rustdesk提供的示例加上自己的rustdesk-api
- 根据rustdesk提供的示例加上自己的rustdesk-api
```docker-compose
networks:
rustdesk-net:
external: false
services:
hbbs:
container_name: hbbs
ports:
- 21115:21115
- 21116:21116 # 自定义 hbbs 映射端口
- 21116:21116/udp # 自定义 hbbs 映射端口
- 21118:21118 # web client 需要
image: rustdesk/rustdesk-server
command: hbbs -r <relay-server-ip[:port]> -k 123456789 # 填入个人域名或 IP + hbbr 暴露端口
volumes:
- /data/rustdesk/hbbs:/root # 自定义挂载目录
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # 自定义 hbbr 映射端口
image: rustdesk/rustdesk-server
command: hbbr -k 123456789
#command: hbbr
volumes:
- /data/rustdesk/hbbr:/root # 自定义挂载目录
networks:
- rustdesk-net
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
rustdesk-api:
container_name: rustdesk-api
environment:
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
```docker-compose
networks:
rustdesk-net:
external: false
services:
hbbs:
container_name: hbbs
ports:
- 21115:21115
- 21116:21116 # 自定义 hbbs 映射端口
- 21116:21116/udp # 自定义 hbbs 映射端口
- 21118:21118 # web client 需要
image: rustdesk/rustdesk-server
command: hbbs -r <relay-server-ip[:port]> -k 123456789 # 填入个人域名或 IP + hbbr 暴露端口
volumes:
- /data/rustdesk/hbbs:/root # 自定义挂载目录
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # 自定义 hbbr 映射端口
image: rustdesk/rustdesk-server
command: hbbr -k 123456789
#command: hbbr
volumes:
- /data/rustdesk/hbbr:/root # 自定义挂载目录
networks:
- rustdesk-net
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
rustdesk-api:
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
#### 下载release直接运行

View File

@@ -1,6 +1,7 @@
# RustDesk API
This project implements RustDesk's API using Go and includes a Web UI and Web client. RustDesk is a remote desktop software that provides a self-hosting solution.
This project implements the RustDesk API using Go, and includes both a web UI and web client. RustDesk is a remote
desktop software that provides self-hosted solutions.
<div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/>
@@ -9,64 +10,99 @@ This project implements RustDesk's API using Go and includes a Web UI and Web cl
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
</div>
## Preparation
## Prerequisites
### [Rustdesk](https://github.com/rustdesk/rustdesk)
1. The PC client version used is ***1.3.0***, and versions ***1.2.6+*** have been tested to work.
2. The server must specify a key and not use the built-in generated key; otherwise, connection issues or timeouts may occur.
2. The server must specify a key, and not use the auto-generated key, otherwise there may be connection failures or
timeouts.
```bash
hbbs -r <relay-server-ip[:port]> -k 123456789
hbbr -k 123456789
```
```bash
hbbs -r <relay-server-ip[:port]> -k <key>
hbbr -k <key>
```
Example:
```bash
hbbs -r <relay-server-ip[:port]> -k abc1234567
hbbr -k abc1234567
```
## Features
### **API Service**: Implements the basic interfaces for the PC client.
### API Service: Basic implementation of the PC client's primary interfaces.
#### Login
- Added `GitHub` and `Google` login, which can be used after configuration in the admin panel. See the OAuth configuration section
for details.
- Added authorization login for the web admin panel.
![pc_login](docs/pc_login.png)
#### Address Book
![pc_ab](docs/pc_ab.png)
#### Groups: Groups are divided into `shared groups` and `regular groups`. In shared groups, everyone can see the addresses of all group members, while in regular groups, only administrators can see all members' addresses.
![pc_gr](docs/pc_gr.png)
### **Web UI**: Uses a front-end and back-end separation, providing a user-friendly management interface primarily for administration and display.
### **Web UI**: The frontend and backend are separated to provide a user-friendly management interface, primarily for managing and displaying data.
***The front-end code is available at [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web).***
***Frontend code is available at [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
***The admin panel can be accessed at `http://<your server>:21114/_admin/` with default credentials of `admin admin`. Please change the password promptly.***
***Admin panel URL: `http://<your server>[:port]/_admin/`. The default username and password for the initial
installation are `admin` `admin`, please change the password immediately.***
1. Admin interface
1. Admin interface:
![web_admin](docs/web_admin.png)
2. Regular user interface
![web_user](docs/web_user.png)
3. Password can be changed from the top-right corner
2. Regular user interface:
![web_user](docs/web_admin_user.png)
3. You can change your password from the top right corner:
![web_resetpwd](docs/web_resetpwd.png)
4. Groups can be customized for easier management. Two types of groups are currently supported: `Shared Group` and `Regular Group`.
4. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`.
![web_admin_gr](docs/web_admin_gr.png)
5. You can open the web client directly for convenience:
![web_webclient](docs/admin_webclient.png)
6. OAuth support: Currently, `GitHub` and `Google` is supported. You need to create an `OAuth App` and configure it in the admin
panel.
![web_admin_oauth](docs/web_admin_oauth.png)
- Create a `GitHub OAuth App`
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`,
e.g., `http://127.0.0.1:21114/api/oauth/callback`.
### **Web Client**:
1. If you are already logged in to the admin panel, the web client will automatically log in.
2. If not logged in, click the login button in the top-right corner; the API server will be auto-configured.
3. Once logged into the admin panel, the address book will be saved automatically in the web client for convenience.
1. If you're already logged into the admin panel, the web client will log in automatically.
2. If you're not logged in, simply click the login button at the top right corner, and the API server will be
pre-configured.
![webclient_conf](docs/webclient_conf.png)
3. After logging in, the ID server and key will be automatically synced.
4. The address book will also be automatically saved to the web client for convenient use.
### **Automated Documentation**: API documentation is generated using Swag, making it easier for developers to understand and use the API.
### **Automated Documentation** : API documentation is generated using Swag, making it easier for developers to understand and use the API.
1. Admin documentation: `<your server>/admin/swagger/index.html`
2. PC client documentation: `<your server>/swagger/index.html`
1. Admin panel docs: `<your server>/admin/swagger/index.html`
2. PC client docs: `<your server>/swagger/index.html`
![api_swag](docs/api_swag.png)
## Installation and Running
## Installation and Setup
### Configuration
* Refer to the `conf/config.yaml` file to modify relevant configurations. If `gorm.type` is `sqlite`, MySQL configurations are not required.
* Modify the configuration in `conf/config.yaml`. If `gorm.type` is set to `sqlite`, MySQL-related configurations are
not required.
```yaml
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
@@ -83,88 +119,128 @@ rustdesk:
key: "123456789"
```
* Environment variables, with the prefix `RUSTDESK_API`, will override the settings in the configuration file if
present.
| Variable Name | Description | Example |
|------------------------------------|-----------------------------------------------------------|--------------------------------|
| ----- GIN Configuration ----- | --------------------------------------- | ------------------------------ |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ------------------------------ |
| 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_OPEN_CONNS | Maximum open connections | 100 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ------------------------------ |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ------------------------------ |
| 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_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
### Installation Steps
#### Running with Docker
#### Running via Docker
1. Run directly using Docker:
1. Run directly with Docker. Configuration can be modified by mounting the config file `/app/conf/config.yaml`, or by
using environment variables to override settings.
```bash
docker run -d --name rustdesk-api -p 21114:21114 -v /data/rustdesk/api:/app/data lejianwen/rustdesk-api
docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=123456789 \
lejianwen/rustdesk-api
```
- Environment variables with the prefix `RUSTDESK_API` can be set.
2. Using `docker-compose`
| Variable Name | Description | Example |
|----------------------------------------|--------------------------------------------------|------------------------------|
| -----------GORM Configuration----------| -------------------------------------------------| -----------------------------|
| RUSTDESK_API_GORM_TYPE | Database type, either `sqlite` or `mysql`. Default is `sqlite` | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum number of idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum number of open connections | 100 |
| -----------MySQL Configuration---------| ---Not required if using `sqlite`--- | |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| -----------Rustdesk Configuration------| -------------------------------------------------| -----------------------------|
| 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_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
- Simple example:
2. Use `docker-compose`, adding your RustDesk API configuration to the provided RustDesk example:
```docker-compose
services:
rustdesk-api:
container_name: rustdesk-api
environment:
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data # Mount the database for easy backup
networks:
- rustdesk-net
restart: unless-stopped
```
```docker-compose
networks:
rustdesk-net:
external: false
services:
hbbs:
container_name: hbbs
ports:
- 21115:21115
- 21116:21116 # Custom hbbs port mapping
- 21116:21116/udp # Custom hbbs port mapping
- 21118:21118 # Required for web client
image: rustdesk/rustdesk-server
command: hbbs -r <relay-server-ip[:port]> -k 123456789 # Use your domain or IP + hbbr exposed port
volumes:
- /data/rustdesk/hbbs:/root # Custom mount directory
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # Custom hbbr port mapping
image: rustdesk/rustdesk-server
command: hbbr -k 123456789
volumes:
- /data/rustdesk/hbbr:/root # Custom mount directory
networks:
- rustdesk-net
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
rustdesk-api:
container_name: rustdesk-api
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data # Mount database for easy backups
networks:
- rustdesk-net
restart: unless-stopped
```
- Example with RustDesk's official Docker Compose file, adding your `rustdesk-api` service:
```docker-compose
networks:
rustdesk-net:
external: false
services:
hbbs:
container_name: hbbs
ports:
- 21115:21115
- 21116:21116 # 自定义 hbbs 映射端口
- 21116:21116/udp # 自定义 hbbs 映射端口
- 21118:21118 # web client 需要
image: rustdesk/rustdesk-server
command: hbbs -r <relay-server-ip[:port]> -k 123456789 # 填入个人域名或 IP + hbbr 暴露端口
volumes:
- /data/rustdesk/hbbs:/root # 自定义挂载目录
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # 自定义 hbbr 映射端口
image: rustdesk/rustdesk-server
command: hbbr -k 123456789
#command: hbbr
volumes:
- /data/rustdesk/hbbr:/root # 自定义挂载目录
networks:
- rustdesk-net
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
rustdesk-api:
container_name: rustdesk-api
environment:
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
#### Running from Release
@@ -186,7 +262,8 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
go install github.com/swaggo/swag/cmd/swag@latest
```
3. Build the admin front-end (the front-end code is in [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)):
3. Build the admin front-end (the front-end code is
in [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)):
```bash
cd resources
mkdir -p admin
@@ -205,9 +282,12 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
go generate generate_api.go
```
5. To compile, change to the project root directory. For Windows, run `build.bat`, and for Linux, run `build.sh`. After compiling, the corresponding executables will be generated in the `release` directory. Run the compiled executables directly.
5. To compile, change to the project root directory. For Windows, run `build.bat`, and for Linux, run `build.sh`. After
compiling, the corresponding executables will be generated in the `release` directory. Run the compiled executables
directly.
6. Open your browser and visit `http://<your server>:21114/_admin/`, with default credentials `admin admin`. Please change the password promptly.
6. Open your browser and visit `http://<your server>:21114/_admin/`, with default credentials `admin admin`. Please
change the password promptly.
## Miscellaneous

View File

@@ -19,7 +19,7 @@ rustdesk:
key: "123456789"
logger:
path: "./runtime/log.txt"
level: "error" #trace,debug,info,warn,error,fatal
level: "warn" #trace,debug,info,warn,error,fatal
report-caller: true
redis:
addr: "127.0.0.1:6379"

View File

@@ -3,6 +3,7 @@ services:
image: lejianwen/rustdesk-api
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/web_admin_oauth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

2
go.mod
View File

@@ -17,6 +17,7 @@ require (
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
golang.org/x/oauth2 v0.23.0
gorm.io/driver/mysql v1.5.7
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.7
@@ -64,7 +65,6 @@ require (
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.7.0 // indirect

View File

@@ -65,7 +65,7 @@ func (ct *Tag) Create(c *gin.Context) {
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) {
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
}
err := service.AllService.TagService.Create(t)

View File

@@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"strings"
)
type Oauth struct {
@@ -161,9 +162,8 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
}
c.String(http.StatusOK, "绑定成功")
return
}
//登录
if ac == service.OauthActionTypeLogin {
} else if ac == service.OauthActionTypeLogin {
//登录
if v.UserId != 0 {
c.String(http.StatusInternalServerError, "授权已经成功")
return
@@ -181,7 +181,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
}
//自动注册
u = service.AllService.UserService.RegisterByGithub(userData.Login, int64(userData.Id))
u = service.AllService.UserService.RegisterByGithub(userData.Login, strconv.Itoa(userData.Id))
if u.Id == 0 {
c.String(http.StatusInternalServerError, "注册失败")
return
@@ -193,19 +193,76 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
c.String(http.StatusOK, "授权成功")
return
}
//返回js
c.Header("Content-Type", "text/html; charset=utf-8")
c.String(http.StatusOK, "授权错误")
//up := &apiResp.UserPayload{}
//c.JSON(http.StatusOK, apiResp.LoginRes{
// AccessToken: ut.Token,
// Type: "access_token",
// User: *up.FromUser(u),
//})
}
if ty == model.OauthTypeGoogle {
code := c.Query("code")
err, userData := service.AllService.OauthService.GoogleCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, "授权失败:"+err.Error())
return
}
//将空格替换成_
googleName := strings.Replace(userData.Name, " ", "_", -1)
if ac == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Email)
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, "已经绑定其他账号")
return
}
//绑定
u := service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, "用户不存在")
return
}
//绑定
err = service.AllService.OauthService.BindGoogleUser(userData.Email, googleName, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, "绑定失败")
return
}
c.String(http.StatusOK, "绑定成功")
return
} else if ac == service.OauthActionTypeLogin {
if v.UserId != 0 {
c.String(http.StatusInternalServerError, "授权已经成功")
return
}
u := service.AllService.UserService.InfoByGoogleEmail(userData.Email)
if u == nil {
oa := service.AllService.OauthService.InfoByOp(ty)
if !*oa.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
v.ThirdName = googleName
v.ThirdOpenId = userData.Email
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
c.Redirect(http.StatusFound, url)
return
}
//自动注册
u = service.AllService.UserService.RegisterByGoogle(googleName, userData.Email)
if u.Id == 0 {
c.String(http.StatusInternalServerError, "注册失败")
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
c.String(http.StatusOK, "授权成功")
return
}
}
c.String(http.StatusInternalServerError, "授权配置错误,请联系管理员")
}
// WebOauthLogin

View File

@@ -12,6 +12,15 @@ func (i *Index) ConfigJs(c *gin.Context) {
apiServer := global.Config.Rustdesk.ApiServer
tmp := `
function stringToUint8Array(str){
var arr = [];
for (var i = 0, j = str.length; i < j; ++i) {
arr.push(str.charCodeAt(i));
}
var tmpUint8Array = new Uint8Array(arr);
return tmpUint8Array
}
window._gwen = {}
window._gwen.kv = {}
function getQueryVariable() {
@@ -53,13 +62,26 @@ const autoWriteServer = () => {
}
if (res.data.peers) {
oldPeers = JSON.parse(localStorage.getItem('peers')) || {}
oldPeers = JSON.parse(localStorage.getItem('peers')) || {}
let needUpdate = false
Object.keys(res.data.peers).forEach(k => {
if(!oldPeers[k]) {
oldPeers[k] = res.data.peers[k]
needUpdate = true
}else{
oldPeers[k].info = res.data.peers[k].info
}
if (oldPeers[k].info && oldPeers[k].info.hash&&!oldPeers[k].password ) {
let p1 = window.atob(oldPeers[k].info.hash)
const pwd = stringToUint8Array(p1)
oldPeers[k].password = pwd.toString()
oldPeers[k].remember = true
}
})
localStorage.setItem('peers', JSON.stringify(oldPeers))
if(needUpdate) {
window.location.reload()
}
}
}
})

View File

@@ -6,7 +6,7 @@ type TagForm struct {
Id uint `json:"id"`
Name string `json:"name" validate:"required"`
Color uint `json:"color" validate:"required"`
UserId uint `json:"user_id" validate:"required"`
UserId uint `json:"user_id"`
}
func (f *TagForm) FromTag(group *model.Tag) *TagForm {

View File

@@ -15,6 +15,7 @@ type WebClientPeerInfoPayload struct {
Username string `json:"username"`
Hostname string `json:"hostname"`
Platform string `json:"platform"`
Hash string `json:"hash"`
}
func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) {
@@ -25,5 +26,6 @@ func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) {
Username: a.Username,
Hostname: a.Hostname,
Platform: a.Platform,
Hash: a.Hash,
}
}

View File

@@ -19,9 +19,6 @@ func ApiInit(g *gin.Engine) {
frg := g.Group("/api")
//frg.Use(middleware.Cors())
frg.OPTIONS("/*any", nil)
i := &api.Index{}
frg.GET("/", i.Index)

View File

@@ -42,25 +42,6 @@
<link rel="modulepreload" href="js/dist/vendor.js">
<script src="yuv-canvas-1.2.6.js"></script>
<script>
// localStorage.setItem('api-server','http://127.0.0.1:21114')
// localStorage.setItem('access_token','0bf218eefa2ac1cdfd72b67f14862bb8')
// window.setByName
// localStorage.setItem('user_info', JSON.stringify({name: "admin", email: "", note: "", is_admin: true, status: 1}))
//peers
// localStorage.setItem('peers',JSON.stringify(
// {
// "1799928825":{"view-style":"shrink","tm":1726154051748,"info":{"username":"android","hostname":"Xiaomi-MI 8 SE","platform":"Android","displays":[{"x":0,"y":0,"width":1080,"height":2244,"name":"Android","online":true}],"current_display":0,"sas_enabled":false,"version":"1.3.0","conn_id":0,"features":{"privacy_mode":false}}},
// "ljwzhuwo":{"view-style":"shrink","tm":1726153696760,"info":{"username":"lejianwen","hostname":"zhuwo","platform":"Windows","displays":[{"x":0,"y":0,"width":2560,"height":1440,"name":"\\\\.\\DISPLAY1","online":true}],"current_display":0,"sas_enabled":false,"version":"1.2.6","conn_id":0,"features":{"privacy_mode":true}}},
// "shenglan":{"view-style":"shrink","tm":1726154006341,"info":{"username":"ttt","hostname":"test","platform":"Windows"}}
// }
// )
// )
//{"1799928825":{"view-style":"shrink","tm":1726154051748,"info":{"username":"android","hostname":"Xiaomi-MI 8 SE","platform":"Android","displays":[{"x":0,"y":0,"width":1080,"height":2244,"name":"Android","online":true}],"current_display":0,"sas_enabled":false,"version":"1.3.0","conn_id":0,"features":{"privacy_mode":false}}},"ljwzhuwo":{"view-style":"shrink","tm":1726153696760,"info":{"username":"lejianwen","hostname":"zhuwo","platform":"Windows","displays":[{"x":0,"y":0,"width":2560,"height":1440,"name":"\\\\.\\DISPLAY1","online":true}],"current_display":0,"sas_enabled":false,"version":"1.2.6","conn_id":0,"features":{"privacy_mode":true}}},"shenglan":{"view-style":"shrink","tm":1726154006341}}
</script>
<style>
.loading {
display: flex;

View File

@@ -68,7 +68,15 @@ type GithubUserdata struct {
UpdatedAt time.Time `json:"updated_at"`
Url string `json:"url"`
}
type GoogleUserdata struct {
Email string `json:"email"`
FamilyName string `json:"family_name"`
GivenName string `json:"given_name"`
Id string `json:"id"`
Name string `json:"name"`
Picture string `json:"picture"`
VerifiedEmail bool `json:"verified_email"`
}
type OauthCacheItem struct {
UserId uint `json:"user_id"`
Id string `json:"id"` //rustdesk的设备ID
@@ -187,7 +195,7 @@ func (os *OauthService) GithubCallback(code string) (error error, userData *Gith
}(resp.Body)
// 在这里处理 GitHub 用户信息
if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil {
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
global.Logger.Warn("failed decoding user info: %s\n", err)
error = errors.New("解析user info失败")
return
@@ -195,6 +203,37 @@ func (os *OauthService) GithubCallback(code string) (error error, userData *Gith
return
}
func (os *OauthService) GoogleCallback(code string) (error error, userData *GoogleUserdata) {
err, oauthConfig := os.GetOauthConfig(model.OauthTypeGoogle)
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
global.Logger.Warn(fmt.Printf("oauthConfig.Exchange() failed: %s\n", err))
error = errors.New("获取token失败")
return
}
// 创建 HTTP 客户端,并将 access_token 添加到 Authorization 头中
client := oauthConfig.Client(context.Background(), token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
global.Logger.Warn("failed getting user info: %s\n", err)
error = errors.New("获取user info失败 " + err.Error())
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
global.Logger.Warn("failed closing response body: %s\n", err)
}
}(resp.Body)
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
global.Logger.Warn("failed decoding user info: %s\n", err)
error = errors.New("解析user info失败" + err.Error())
return
}
return
}
func (os *OauthService) UserThirdInfo(op, openid string) *model.UserThird {
ut := &model.UserThird{}
global.DB.Where("open_id = ? and third_type = ?", openid, op).First(ut)
@@ -202,14 +241,22 @@ func (os *OauthService) UserThirdInfo(op, openid string) *model.UserThird {
}
func (os *OauthService) BindGithubUser(openid, username string, userId uint) error {
return os.BindOauthUser(model.OauthTypeGithub, openid, username, userId)
}
func (os *OauthService) BindGoogleUser(email, username string, userId uint) error {
return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId)
}
func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error {
utr := &model.UserThird{
OpenId: openid,
ThirdType: model.OauthTypeGithub,
ThirdType: thirdType,
ThirdName: username,
UserId: userId,
}
return global.DB.Create(utr).Error
}
func (os *OauthService) UnBindGithubUser(userid uint) error {
return global.DB.Where("user_id = ? and third_type = ?", userid, model.OauthTypeGithub).Delete(&model.UserThird{}).Error
}

View File

@@ -175,7 +175,17 @@ func (us *UserService) RouteNames(u *model.User) []string {
// InfoByGithubId 根据githubid取用户信息
func (us *UserService) InfoByGithubId(githubId string) *model.User {
ut := AllService.OauthService.UserThirdInfo(model.OauthTypeGithub, githubId)
return us.InfoByOauthId(model.OauthTypeGithub, githubId)
}
// InfoByGoogleEmail 根据googleid取用户信息
func (us *UserService) InfoByGoogleEmail(email string) *model.User {
return us.InfoByOauthId(model.OauthTypeGithub, email)
}
// InfoByOauthId 根据oauth取用户信息
func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User {
ut := AllService.OauthService.UserThirdInfo(thirdType, uid)
if ut.Id == 0 {
return nil
}
@@ -187,12 +197,22 @@ func (us *UserService) InfoByGithubId(githubId string) *model.User {
}
// RegisterByGithub 注册
func (us *UserService) RegisterByGithub(githubName string, githubId int64) *model.User {
func (us *UserService) RegisterByGithub(githubName string, githubId string) *model.User {
return us.RegisterByOauth(model.OauthTypeGithub, githubName, githubId)
}
// RegisterByGoogle 注册
func (us *UserService) RegisterByGoogle(name string, email string) *model.User {
return us.RegisterByOauth(model.OauthTypeGoogle, name, email)
}
// RegisterByOauth 注册
func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User {
tx := global.DB.Begin()
ut := &model.UserThird{
OpenId: strconv.FormatInt(githubId, 10),
ThirdName: githubName,
ThirdType: model.OauthTypeGithub,
OpenId: uid,
ThirdName: thirdName,
ThirdType: thirdType,
}
//global.DB.Where("open_id = ?", githubId).First(ut)
//这种情况不应该出现如果出现说明有bug
@@ -203,7 +223,7 @@ func (us *UserService) RegisterByGithub(githubName string, githubId int64) *mode
// return u
//}
username := us.GenerateUsernameByOauth(githubName)
username := us.GenerateUsernameByOauth(thirdName)
u := &model.User{
Username: username,
GroupId: 1,