From b973db78e2d0375bcd3ca624a49eb09a50dd2bfa Mon Sep 17 00:00:00 2001 From: Meng Sen Date: Sun, 27 Apr 2025 11:14:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=20RustDesk-API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Meng Sen --- apps/rustdesk-api/2.6.16/conf/hbbr/run | 5 + apps/rustdesk-api/2.6.16/conf/hbbs/run | 6 + apps/rustdesk-api/2.6.16/data.yml | 154 ++++++++++++++++++ apps/rustdesk-api/2.6.16/docker-compose.yml | 51 ++++++ apps/rustdesk-api/2.6.16/envs/default.env | 2 + apps/rustdesk-api/2.6.16/envs/global.env | 2 + apps/rustdesk-api/2.6.16/scripts/init.sh | 17 ++ apps/rustdesk-api/2.6.16/scripts/uninstall.sh | 10 ++ apps/rustdesk-api/2.6.16/scripts/upgrade.sh | 17 ++ apps/rustdesk-api/README.md | 34 ++++ apps/rustdesk-api/data.yml | 14 ++ apps/rustdesk-api/logo.png | Bin 0 -> 7689 bytes 12 files changed, 312 insertions(+) create mode 100644 apps/rustdesk-api/2.6.16/conf/hbbr/run create mode 100644 apps/rustdesk-api/2.6.16/conf/hbbs/run create mode 100644 apps/rustdesk-api/2.6.16/data.yml create mode 100644 apps/rustdesk-api/2.6.16/docker-compose.yml create mode 100644 apps/rustdesk-api/2.6.16/envs/default.env create mode 100644 apps/rustdesk-api/2.6.16/envs/global.env create mode 100644 apps/rustdesk-api/2.6.16/scripts/init.sh create mode 100644 apps/rustdesk-api/2.6.16/scripts/uninstall.sh create mode 100644 apps/rustdesk-api/2.6.16/scripts/upgrade.sh create mode 100644 apps/rustdesk-api/README.md create mode 100644 apps/rustdesk-api/data.yml create mode 100644 apps/rustdesk-api/logo.png diff --git a/apps/rustdesk-api/2.6.16/conf/hbbr/run b/apps/rustdesk-api/2.6.16/conf/hbbr/run new file mode 100644 index 000000000..7d6025653 --- /dev/null +++ b/apps/rustdesk-api/2.6.16/conf/hbbr/run @@ -0,0 +1,5 @@ +#!/command/with-contenv sh +cd /data || exit +PARAMS= +[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}" +/usr/bin/hbbr $PARAMS diff --git a/apps/rustdesk-api/2.6.16/conf/hbbs/run b/apps/rustdesk-api/2.6.16/conf/hbbs/run new file mode 100644 index 000000000..44cdd5261 --- /dev/null +++ b/apps/rustdesk-api/2.6.16/conf/hbbs/run @@ -0,0 +1,6 @@ +#!/command/with-contenv sh +sleep 2 +cd /data +PARAMS= +[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}" +/usr/bin/hbbs -r $RELAY $PARAMS diff --git a/apps/rustdesk-api/2.6.16/data.yml b/apps/rustdesk-api/2.6.16/data.yml new file mode 100644 index 000000000..b0815cfb5 --- /dev/null +++ b/apps/rustdesk-api/2.6.16/data.yml @@ -0,0 +1,154 @@ +additionalProperties: + formFields: + - default: "/home/rustdesk-api" + edit: true + envKey: RUSTDESK_API_ROOT_PATH + labelZh: 数据持久化路径 + labelEn: Data persistence path + required: true + type: text + - default: 21114 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelZh: WebUI 端口 + labelEn: WebUI port + required: true + rule: paramPort + type: number + - default: "http://127.0.0.1" + edit: true + envKey: RUSTDESK_SERVER_URL + labelZh: RustDesk 服务地址 + labelEn: RustDesk server address + required: true + type: text + - default: "RustDesk API Admin" + edit: true + envKey: RUSTDESK_API_ADMIN_TITLE + labelZh: 后台页面标题 + labelEn: Admin page title + required: true + type: text + - default: "" + edit: true + envKey: RUSTDESK_API_RUSTDESK_KEY + labelZh: RustDesk API 密钥 + labelEn: RustDesk API key + required: true + type: text + - default: "168h" + edit: true + envKey: RUSTDESK_API_APP_TOKEN_EXPIRE + labelZh: 登录有效期 + labelEn: Login validity period + required: true + type: text + - default: "1" + edit: true + envKey: RUSTDESK_API_APP_WEB_CLIENT + labelZh: 启用 Web Client + labelEn: Enable Web Client + required: true + type: select + values: + - label: 启用 + value: "1" + - label: 禁用 + value: "0" + - default: "1" + edit: true + envKey: RUSTDESK_API_APP_REGISTER + labelZh: 开启 Swagger 文档 + labelEn: Enable Swagger docs + required: true + type: select + values: + - label: 启用 + value: "1" + - label: 禁用 + value: "0" + - default: "false" + edit: true + envKey: RUSTDESK_API_APP_REGISTER + labelZh: 启用注册 + labelEn: Enable register + required: true + type: select + values: + - label: 启用 + value: "true" + - label: 禁用 + value: "false" + - default: "false" + edit: true + envKey: RUSTDESK_API_APP_DISABLE_PWD_LOGIN + labelZh: 登录策略 + labelEn: Login strategy + required: true + type: select + values: + - label: 禁用密码登录 + value: "true" + - label: 允许密码登录 + value: "false" + - default: "false" + edit: true + envKey: RUSTDESK_API_PROXY_ENABLE + labelZh: 启用代理 + labelEn: Enable proxy + required: true + type: select + values: + - label: 启用 + value: "true" + - label: 禁用 + value: "false" + - default: "" + edit: true + envKey: RUSTDESK_API_PROXY_HOST + labelZh: 代理地址 + labelEn: Proxy address + required: false + type: text + - default: "sqlite" + edit: true + envKey: RUSTDESK_API_GORM_TYPE + labelZh: 数据库类型 + labelEn: Database type + required: true + type: select + values: + - label: sqlite + value: "sqlite" + - label: MySQL + value: "false" + - default: "127.0.0.1:3306" + edit: true + envKey: RUSTDESK_API_MYSQL_ADDR + labelZh: 数据库地址 + labelEn: Database Host + required: false + type: text + - default: "rustdesk" + edit: true + envKey: RUSTDESK_API_MYSQL_DBNAME + labelZh: 数据库 名称 + labelEn: Database Name + required: false + rule: paramCommon + type: text + - default: "rustdesk" + edit: true + envKey: RUSTDESK_API_MYSQL_USERNAME + labelZh: 数据库 用户名 + labelEn: Database Username + required: false + type: text + - default: "" + edit: true + envKey: RUSTDESK_API_MYSQL_PASSWORD + labelZh: 数据库 密码 + labelEn: Database Password + required: false + rule: paramComplexity + type: password diff --git a/apps/rustdesk-api/2.6.16/docker-compose.yml b/apps/rustdesk-api/2.6.16/docker-compose.yml new file mode 100644 index 000000000..74d93f586 --- /dev/null +++ b/apps/rustdesk-api/2.6.16/docker-compose.yml @@ -0,0 +1,51 @@ +networks: + 1panel-network: + external: true + +services: + rustdesk-api: + image: lejianwen/rustdesk-api:v2.6.16 + container_name: ${CONTAINER_NAME} + labels: + createdBy: "Apps" + restart: always + networks: + - 1panel-network + ports: + - ${PANEL_APP_PORT_HTTP}:21114 + env_file: + - ${GLOBAL_ENV_FILE:-/etc/1panel/envs/global.env} + - ${ENV_FILE:-/etc/1panel/envs/default.env} + volumes: + - ${RUSTDESK_API_ROOT_PATH}/data:/data + environment: + - TZ=Asia/Shanghai + - RUSTDESK_API_LANG=zh-CN + - RUSTDESK_API_RUSTDESK_ID_SERVER=${RUSTDESK_SERVER_URL}:21116 + - RUSTDESK_API_RUSTDESK_RELAY_SERVER=${RUSTDESK_SERVER_URL}:21117 + - RUSTDESK_API_RUSTDESK_API_SERVER=${RUSTDESK_SERVER_URL}:${PANEL_APP_PORT_HTTP:-21114} + rustdesk-server-s6: + image: rustdesk/rustdesk-server-s6:latest + container_name: server-${CONTAINER_NAME} + labels: + createdBy: "Apps" + restart: always + networks: + - 1panel-network + ports: + - 21115:21115 + - 21116:21116 + - 21116:21116/udp + - 21117:21117 + - 21118:21118 + - 21119:21119 + env_file: + - ${GLOBAL_ENV_FILE:-/etc/1panel/envs/global.env} + - ${ENV_FILE:-/etc/1panel/envs/default.env} + volumes: + - ${RUSTDESK_API_ROOT_PATH}/data:/data + environment: + - TZ=Asia/Shanghai + - RUSTDESK_API_LANG=zh-CN + - RELAY=${RUSTDESK_SERVER_URL}:21117 + - ENCRYPTED_ONLY=1 diff --git a/apps/rustdesk-api/2.6.16/envs/default.env b/apps/rustdesk-api/2.6.16/envs/default.env new file mode 100644 index 000000000..cd05f46e6 --- /dev/null +++ b/apps/rustdesk-api/2.6.16/envs/default.env @@ -0,0 +1,2 @@ +# copyright© 2024 XinJiang Ms Studio +ENV_FILE=.env diff --git a/apps/rustdesk-api/2.6.16/envs/global.env b/apps/rustdesk-api/2.6.16/envs/global.env new file mode 100644 index 000000000..e10989fe4 --- /dev/null +++ b/apps/rustdesk-api/2.6.16/envs/global.env @@ -0,0 +1,2 @@ +# copyright© 2024 XinJiang Ms Studio +TZ=Asia/Shanghai diff --git a/apps/rustdesk-api/2.6.16/scripts/init.sh b/apps/rustdesk-api/2.6.16/scripts/init.sh new file mode 100644 index 000000000..07fb8c3fe --- /dev/null +++ b/apps/rustdesk-api/2.6.16/scripts/init.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ -f .env ]; then + source .env + + # setup-1 add default values + CURRENT_DIR=$(pwd) + sed -i '/^ENV_FILE=/d' .env + sed -i '/^GLOBAL_ENV_FILE=/d' .env + echo "ENV_FILE=${CURRENT_DIR}/.env" >> .env + echo "GLOBAL_ENV_FILE=${CURRENT_DIR}/envs/global.env" >> .env + + echo "Check Finish." + +else + echo "Error: .env file not found." +fi diff --git a/apps/rustdesk-api/2.6.16/scripts/uninstall.sh b/apps/rustdesk-api/2.6.16/scripts/uninstall.sh new file mode 100644 index 000000000..c86c4fbca --- /dev/null +++ b/apps/rustdesk-api/2.6.16/scripts/uninstall.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +if [ -f .env ]; then + source .env + + echo "Check Finish." + +else + echo "Error: .env file not found." +fi diff --git a/apps/rustdesk-api/2.6.16/scripts/upgrade.sh b/apps/rustdesk-api/2.6.16/scripts/upgrade.sh new file mode 100644 index 000000000..07fb8c3fe --- /dev/null +++ b/apps/rustdesk-api/2.6.16/scripts/upgrade.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ -f .env ]; then + source .env + + # setup-1 add default values + CURRENT_DIR=$(pwd) + sed -i '/^ENV_FILE=/d' .env + sed -i '/^GLOBAL_ENV_FILE=/d' .env + echo "ENV_FILE=${CURRENT_DIR}/.env" >> .env + echo "GLOBAL_ENV_FILE=${CURRENT_DIR}/envs/global.env" >> .env + + echo "Check Finish." + +else + echo "Error: .env file not found." +fi diff --git a/apps/rustdesk-api/README.md b/apps/rustdesk-api/README.md new file mode 100644 index 000000000..6fc90b387 --- /dev/null +++ b/apps/rustdesk-api/README.md @@ -0,0 +1,34 @@ +# RustDesk API + +RustDesk 是一款远程访问和远程控制软件 + +![RustDesk API](https://file.lifebus.top/imgs/rustdesk-api_cover.png) + +![](https://img.shields.io/badge/%E6%96%B0%E7%96%86%E8%90%8C%E6%A3%AE%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E5%B7%A5%E4%BD%9C%E5%AE%A4-%E6%8F%90%E4%BE%9B%E6%8A%80%E6%9C%AF%E6%94%AF%E6%8C%81-blue) + +## 简介 + +本项目使用 Go 实现了 RustDesk 的 API,并包含了 Web Admin 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。 + +## 安装说明 + +### 端口 + +RustDesk 服务器自托管所需的端口很大程度上取决于您的环境以及您希望使用 RustDesk 做什么。 + +核心端口: + +| 类型 | 范围 | +|-----|-------------| +| TCP | 21114-21119 | +| UDP | 21116 | + +其中 21115-21117 是 RustDesk 工作所需的最低端口,它们处理信号和中继端口以及 NAT 遍历。 + +TCP 端口 21118 和 21119 是 RustDesk Web 客户端的 WebSocket 端口,您需要一个反向代理才能使其支持 HTTPS。 + +对于没有 SSL 代理的专业用户,您需要打开 TCP 端口 21114 才能使 API 工作,或者使用 SSL 代理打开 TCP 端口 443 。 + +--- + +![Ms Studio](https://file.lifebus.top/imgs/ms_blank_001.png) diff --git a/apps/rustdesk-api/data.yml b/apps/rustdesk-api/data.yml new file mode 100644 index 000000000..627dd7442 --- /dev/null +++ b/apps/rustdesk-api/data.yml @@ -0,0 +1,14 @@ +additionalProperties: + key: rustdesk-api + name: RustDesk API + tags: + - WebSite + - Local + shortDescZh: RustDesk 的 API 管理平台 + shortDescEn: RustDesk API management platform + type: website + crossVersionUpdate: true + limit: 0 + website: https://rustdesk.com/ + github: https://github.com/lejianwen/rustdesk-api/ + document: https://github.com/lejianwen/rustdesk-api/ diff --git a/apps/rustdesk-api/logo.png b/apps/rustdesk-api/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..89abf23a68ae85d0f28cdb90d1b08ad36db9d2c8 GIT binary patch literal 7689 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