From a8870c26d50d71c4244018c0ffc62e5fc2bc72c0 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 16 Sep 2026 14:20:23 +0800 Subject: [PATCH] docs: restructure README - put our modifications first, add build steps --- README.md | 519 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 297 insertions(+), 222 deletions(-) diff --git a/README.md b/README.md index cdd56ca..ebeac7e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,303 @@ 本项目使用 Go 实现了 RustDesk 的 API,并包含了 Web Admin 和 Web 客户端。 +--- + +# 本仓库修改 + +## 编译与打包步骤 + +> 完整的服务器编译与镜像打包步骤见 [lejianwen/rustdesk-server README](https://github.com/lejianwen/rustdesk-server)。 +> 本节仅列出 API 侧(apimain + admin 前端 + 完整镜像)的编译步骤。 + +### 1. 编译 API 二进制(apimain) + +```sh +# 启动 Go 编译容器 +docker run -d --name gobuild --network host \ + -v /root/rustdesk/build/apibuild:/srv -w /srv golang:1.23-alpine sleep infinity + +# 安装 git + gcc(CGO 静态编译需要) +docker exec gobuild apk add --no-cache git gcc musl-dev + +# 设置 Go 代理 +docker exec gobuild go env -w GOPROXY=https://goproxy.cn,direct + +# 编译(CGO 静态编译) +docker exec gobuild env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=gcc \ + go build -ldflags '-s -w' -o ./amd64/release/apimain ./cmd/apimain.go +``` + +> 注意:仓库快照的 `go.mod` 与 `go.sum` 可能不同步。若编译报 `missing go.sum entry`, +> 把仓库复制到临时目录执行 `go mod tidy` 后再编译,不动原仓库。 + +### 2. 构建 admin 前端 + +```sh +git clone --depth 1 -b master https://gh-proxy.com/https://github.com/lejianwen/rustdesk-api-web +docker run --rm -v $PWD:/srv -w /srv node:20-alpine sh -c \ + "npm config set registry https://registry.npmmirror.com && npm install && npm run build" +cp -a rustdesk-api-web/dist/. /root/rustdesk/build/apibuild/resources/admin/ +``` + +> Go 二进制从磁盘读 `resources` 目录(不内嵌),所以无需重编 Go,重打镜像层即可。 + +### 3. 打完整镜像 + +```sh +# 修改 Dockerfile_full_s6 首行,指向本地 s6 基础镜像 +cp /root/rustdesk/build/apibuild/Dockerfile_full_s6 /tmp/Dockerfile_full_s6 +sed -i 's|FROM rustdesk/rustdesk-server-s6:latest AS server|FROM rustdesk-server-s6:1.1.17-forapi AS server|' /tmp/Dockerfile_full_s6 + +# 构建(context 必须含 amd64/release) +docker build -f /tmp/Dockerfile_full_s6 \ + --build-arg BUILDARCH=amd64 \ + -t yangjieshao/rustdesk-api:full-s6-amd64 \ + /root/rustdesk/build/apibuild +``` + +### 4. 验证运行 + +```sh +docker run -d --name rustdesk-test -p 21114-21119:21114-21119 \ + -p 21116:21116/udp \ + -e RELAY= \ + -v :/data -v :/app/data \ + yangjieshao/rustdesk-api:full-s6-amd64 + +curl -s http://127.0.0.1:21114/_admin/ -o /dev/null -w '%{http_code}\n' +``` + + +## 感谢你的支持!如果这个项目对你有帮助,请点个⭐️鼓励一下,谢谢! + +[lejianwen/rustdesk-server]: https://github.com/lejianwen/rustdesk-server + +--- + +# 自连拦截(A 远程连 A)API 侧改动 + +> 日期:2026-09-06 +> 关联文档:[lejianwen/rustdesk-server README](https://github.com/lejianwen/rustdesk-server) +> 说明:以下为自连拦截方案中涉及本仓库(rustdesk-api,Go)的代码改动,服务端(hbbs)改动见 [lejianwen/rustdesk-server README](https://github.com/lejianwen/rustdesk-server)。 + +## 改动清单 + +| 文件 | 改动 | +|---|---| +| `service/user.go` | 新增 `GetDeviceIdByToken(token)`:查 UserToken(校验 ExpiredAt)返回 DeviceId | +| `http/controller/api/internal.go` | 新增内部接口 `GET /api/internal/token-device?token=`:仅回环 IP + `X-Secret` 头必须等于 JWT 密钥,否则 403;查不到 404 | +| `http/router/api.go` | 注册路由(置于 RustAuth 中间件之前) | +| `lib/jwt/jwt.go` | GenerateToken 增加 jti(`ID: uuid.NewString()`),每次登录 token 唯一 | + +## 运行配置 + +- API 内部地址可用 `RUSTDESK_API_INTERNAL_URL` 覆盖,默认 `http://127.0.0.1:21114` +- hbbs 与 API 同容器时,`RUSTDESK_API_JWT_KEY` 双方共享(hbbs 的 jwt::SECRET 即 API 的 jwt.key) +- ⚠️ **部署必设 `RUSTDESK_API_JWT_KEY`**(任意字符串)。未设置时 hbbs 的 JWT 密钥为空, + token 校验和自连拦截**整体静默关闭**(fail-open 设计)。修改该值后客户端需退出重新登录。 + +--- + +# RustDesk 服务器合并 1.1.17 与 Docker 镜像构建记录 + +> 日期:2026-09-05 +> 目标:将官方 `rustdesk/rustdesk-server` 1.1.17 的更新合并到 `lejianwen/rustdesk-server`(forapi 分支,带账号 API 支持),并打包出可直接使用的完整 Docker 镜像。 + +## 1. 背景与仓库关系 + +| 仓库 | 说明 | 版本状态 | +|---|---|---| +| `/root/rustdesk/rustdesk/rustdesk-server` | 官方原版(只读,全程未改动) | 1.1.17(2026-08) | +| `/root/rustdesk/lejianwen/rustdesk-server` | 第三方修改版,`forapi` 分支持账号登录/API/JWT | 原基于 1.1.14(2025-09),本次合并到 1.1.17 | +| `/root/rustdesk/lejianwen/rustdesk-api` | Go 编写的 API 服务,`Dockerfile_full_s6` 打完整镜像 | 原样使用 | + +两个 rustdesk-server 仓库在官方 1.1.14(merge-base `c650217`)处分叉: +官方之后有 43 个提交(1.1.15 → 1.1.17),lejianwen 有 38 个自己的提交。 + +**官方 1.1.15~1.1.17 的主要改动**(合并的重点): + +- 安全:禁用 UDP 打洞反射/放大路径(`PunchHoleRequest`/`PunchHoleSent`/`LocalAddr` 只允许走 TCP/WS);中继 X-Real-IP 头欺骗风险注释;升级 mio 0.8.11、protobuf 3.7.2 +- 功能:`-b/--bind` 绑定指定 IP;控制台监听独立绑到 127.0.0.1;`punch-requests(pr)` 连接日志控制台命令 +- 修复:在线状态 `i32` 溢出(运行 24.9~49.7 天后误报在线);中继限速默认值 4Mb/s → 32Mb/s +- 依赖:`hbb_common` 子模块多次推进(最终 `69cea8d`)、新增 `flate2` + +## 2. 合并过程与冲突处理 + +执行 `git merge upstream/master`(在 lejianwen 仓库本地添加 `upstream` 指向官方目录)。 + +| 冲突文件 | 处理方式 | +|---|---| +| `src/rendezvous_server.rs` | 保留 fork 的 JWT/必须登录/KeyExchange/加密 Sink/API 特性,叠加上游全部修复(bind、console listener、UDP 禁用、i64、PUNCH_REQS、`punch-requests` 命令等)。共 4 处冲突块手动合并 | +| `src/main.rs` | 双方参数合并:上游的 `--bind` + DEPRECATED 标注,保留 fork 的 `--must-login` | +| `libs/hbb_common`(子模块) | 从 fork 的旧提交直接指到官方 1.1.17 用的 `69cea8d`。先确认 fork 用到的 `tcp::Encrypt` 接口在新版仍存在 | +| `Cargo.toml` / `Cargo.lock` | git 自动合并成功(1.1.17 + flate2 + 保留 jsonwebtoken/headers);lock 文件 cargo 构建后未再变动,说明自动合并结果一致 | +| `README*.md`、`.github/workflows/*`、`docker-compose.yml`、`docker/Dockerfile` | 取 fork 版本(文档是 API 版、CI 是 lejianwen 自己的发布流程) | + +**合并中发现的注意点**: + +- 上游把 `handle_udp` 里的三个打洞消息改成"故意不支持",fork 的 `handle_udp_punch_hole_request` 变成死代码(仅编译警告),保留不影响行为;webclient 打洞走 TCP/WS 路径不受影响 +- fork 侧 `check_cmd` 帮助文本比上游多一行 `must-login`,合并 `punch-requests` 时要同步把 format! 占位符从 7 个加到 8 个,**否则编译报 `argument never used`**(本次实际踩过) +- 上游的 `peers_online_state` 是内联代码,fork 抽成了 helper(`handle_online_request` 复用),合并时保留 helper 并把 `as i32` 改 `as i64` + +## 3. 构建环境问题与解决(按时间顺序) + +本机只有 `docker` 和 `gcc`,没有 rust/go 工具链,全部编译在容器中进行。 + +### 3.1 容器内访问 GitHub 失败 + +**现象**:容器内 `cargo build` 拉取 git 依赖(`confy` 等 3 个来自 github.com)时 `SSL routines::unexpected eof` / 连接超时,重试 10 次仍失败;直连 `git ls-remote github.com` 间歇性可用。 +**原因**:到 GitHub 的连接被间歇性重置(典型网络环境问题),宿主机偶尔能通但容器内不稳定。 +**解决**: +- 测试可用的加速代理:`gh-proxy.com`、`ghproxy.net`(`ghfast.top` 返回 403) +- 容器内配置系统级改写(注意写 `/etc/gitconfig`,`--global` 在某些 exec 环境不可靠): + +```sh +git config --system url.'https://gh-proxy.com/https://github.com/'.insteadOf 'https://github.com/' +``` + +- s6-overlay 等构建期从 GitHub 下载的文件改为**宿主机预下载**(走代理)后 `COPY` 进镜像,见 3.6 + +### 3.2 Alpine apk 源问题 + +**现象**:`rust:alpine`(Alpine 3.24.1)内 `apk add` 卡住 10 分钟无进展;换清华源后 `HTTP 403: Forbidden`。 +**原因**:官方 CDN 在当前网络环境慢;清华源对 v3.24 索引 403。 +**解决**:使用阿里云源(与镜像版本匹配的 v3.24): + +```sh +printf 'https://mirrors.aliyun.com/alpine/v3.24/main\nhttps://mirrors.aliyun.com/alpine/v3.24/community\n' > /etc/apk/repositories +``` + +注意先确认镜像的 Alpine 版本(`cat /etc/alpine-release`),不要照搬别的版本号。 + +### 3.3 挂载 CARGO_HOME 盖住了 Rust 工具链 + +**现象**:容器内 `cargo: not found`。 +**原因**:把宿主目录挂载到 `/usr/local/cargo`(想持久化缓存),而 `rust:alpine` 的 cargo/rustc 就装在这个路径里,挂载把它们全遮住了。 +**解决**:不挂载该路径,cargo 配置直接写容器内 `/usr/local/cargo/config.toml`;既然用持久化容器(`docker run -d ... sleep infinity` + `docker exec`),缓存本来就不会丢。 + +### 3.4 cargo 的 git-fetch-with-cli 需要 git 二进制 + +**现象**:`could not execute process 'git fetch ...' (never executed) - No such file or directory`。 +**原因**:`rust:alpine` 不带 git,而 config 里设了 `git-fetch-with-cli = true`。 +**解决**:`apk add git`。(该模式值得保留:CLI git 才能吃 `insteadOf` 改写,cargo 内置 libgit2 不认。) + +### 3.5 Go 构建:go.sum 缺条目 + +**现象**:`missing go.sum entry for module providing package github.com/spf13/cobra`。 +**原因**:仓库快照的 `go.mod` 与 `go.sum` 不同步(上游代码在 go.mod 里加了 cobra 但没提交 go.sum 条目)。 +**解决**:把仓库**复制到临时目录**(不动原仓库)执行 `go mod tidy` 后再 `go build`。CGO 静态编译命令: + +```sh +CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=gcc CGO_LDFLAGS='-static' \ + go build -ldflags '-s -w' -o ./amd64/release/apimain ./cmd/apimain.go +cp -ar resources docs conf amd64/release/ && mkdir -p amd64/release/{data,runtime} +``` + +(alpine 容器里 gcc 本身就是 musl-gcc,等价于官方 CI 的 musl-tools 静态构建;产物 `file` 确认 `statically linked`。) + +### 3.6 docker build 阶段无法访问 GitHub(s6-overlay) + +**现象**:官方 `docker/Dockerfile` 用 `ADD https://github.com/just-containers/s6-overlay/releases/download/...` 构建期联网下载。 +**解决**:宿主机预下载(`curl -L https://gh-proxy.com/https://github.com/...`),复制官方 Dockerfile 后把两条 `ADD` 改成本地 `COPY`(不改动官方仓库)。下载物: +- `s6-overlay-noarch.tar.xz` +- `s6-overlay-x86_64.tar.xz` +- 版本 v3.2.0.0(与 Dockerfile 内 `S6_OVERLAY_VERSION` 一致) + +### 3.7 没有 buildx,无法用 --build-context 覆盖 FROM + +**现象**:本机 docker 26.1.4 无 buildx 插件,`docker build --build-context` 不可用。 +**解决**:复制 `Dockerfile_full_s6` 到构建目录,sed 把首行 `FROM rustdesk/rustdesk-server-s6:latest AS server` 改为本地基础镜像名,构建时 `-f` 指向副本、context 指向含 `amd64/release` 的目录。 + +### 3.8 admin 面板 404:resources/admin 不在仓库里 + +**现象**:镜像跑起来后 `/_admin` 返回 404,release 目录里没有 `admin` 静态文件。 +**原因**:admin 前端来自独立仓库 `lejianwen/rustdesk-api-web`,官方 CI 先 checkout 该仓库、`npm run build` 后把 `dist/*` 拷进 `resources/admin/`,这一步在本地打包时缺失。 +**解决**: + +```sh +git clone --depth 1 -b master https://gh-proxy.com/https://github.com/lejianwen/rustdesk-api-web +docker run --rm -v $PWD:/srv -w /srv node:20-alpine sh -c \ + "npm config set registry https://registry.npmmirror.com && npm install && npm run build" +cp -a rustdesk-api-web/dist/. /resources/admin/ +``` + +Go 二进制从磁盘读 `resources` 目录(不内嵌),所以**无需重编 Go**,重打镜像层即可。 + +### 3.9 杂项 + +- **pkill 自杀**:`pkill -f cargo` 会匹配到承载它的 `sh -c "... pkill -f cargo ..."` 自身,返回 143。用 `pkill -f '[c]argo'` 括号模式规避 +- **管道吞退出码**:`cargo build | tail` 拿到的是 tail 的退出码;busybox ash 不支持 bash 的 `PIPESTATUS`。判断成功与否要看日志里的 `Finished`/`error` 行,或不用管道 +- **镜像标签冲突**:为避免与作者发布的 `lejianwen/rustdesk-api:full-s6-amd64` 撞名,最终镜像按需求改为 `yangjieshao/rustdesk-api:full-s6-amd64`(仅本地,未推送) + +## 4. 产物与验证结果 + +### 4.1 镜像 + +| 镜像 | 说明 | +|---|---| +| `yangjieshao/rustdesk-api:full-s6-amd64` | 最终完整镜像(hbbs + hbbr + rustdesk-utils + apimain + admin 前端,s6 托管) | +| `rustdesk-server-s6:1.1.17-forapi` | 本地自建的 s6 基础镜像(对应官方 `rustdesk/rustdesk-server-s6`,内含合并后的 1.1.17 二进制,musl 静态链接) | + +服务器二进制:`hbbs`/`hbbr`/`rustdesk-utils`,`file` 确认 `ELF x86-64 static-pie linked`。 + +### 4.2 实测结论 + +- 21114(API)/21115(NAT)/21116(tcp+udp hbbs)/21117(hbbr)/21118(ws)/21119(ws relay) 全部监听 +- `POST /api/login` 返回业务校验 JSON(接口正常) +- `/_admin/` 返回 200(admin 面板正常) +- s6 healthcheck:3 个服务(hbbs、hbbr、api)全部 up +- `/data` 下正确生成 `id_ed25519(.pub)` 与 `db_v2.sqlite3` +- 日志出现 `LIMIT_SPEED: 32Mb/s`、`Listening on websocket :21119`,确认 1.1.17 代码与 fork 的 relay WS 支持同时生效 + +### 4.3 已知无害现象 + +- 启动日志 `ERROR ... Failed to get machine uid: No such file or directory`:busybox/alpine 容器内没有 dbus machine-id 所致,官方镜像同样存在,hbbs 自动生成 ID 兜底,不影响功能 +- 对 21118/21119 用普通 curl 探测返回 000/连接重置:这两个端口只接受 WebSocket 握手,非 ws 请求被断开是预期行为 + +## 5. 注意事项 + +1. **远端未动**:全程未 push、未登录任何账号。本地仅有两个 git 操作: + - forapi 分支上一条本地合并提交(含格式串修复,已 amend 进合并提交) + - 备份分支 `forapi-backup-pre-merge`(合并前状态,`git reset --hard forapi-backup-pre-merge` 可整体还原) +2. **官方仓库 `/root/rustdesk/rustdesk/rustdesk-server` 全程只读**;镜像构建上下文是复制到 `/root/rustdesk/build/s6img` 后再改的 +3. **代理配置时效性**:`gh-proxy.com`/`rsproxy.cn`/阿里云 apk 源均为当下可用的国内加速,失效时按 3.1/3.2/3.8 的思路换源 +4. **目录占用**:`rustdesk-server/target/` 是编译缓存(数 GB,不需要可删);`/root/cargo-home` 是一次失败尝试的残留(可删);构建相关都在 `/root/rustdesk/build/`(`s6img/`、`fullimg/`、`apibuild/`、`rustdesk-api-web/`、`testdata/`) +5. **测试容器**:`rustdesk-test` 还在运行(测试数据在 `build/testdata`,admin 初始密码 `S2qAgqSw`),`docker rm -f rustdesk-test` 清理 +6. **下次升级的路径**:本次做法可复用——`git fetch upstream && git merge upstream/master`,重点冲突仍会集中在 `src/rendezvous_server.rs`(fork 的 API 特性与上游安全修复叠加);hbb_common 子模块直接指到上游对应提交;编译用同样的容器方案 + +## 6. 常用命令备忘 + +```sh +# 服务器编译(持久容器方案) +docker run -d --name rustbuild --network host \ + -v /root/rustdesk/lejianwen/rustdesk-server:/srv -w /srv rust:alpine sleep infinity +# 容器内:换阿里云 apk 源 → apk add build-base pkgconf openssl-dev openssl-libs-static git +# /usr/local/cargo/config.toml 配置 rsproxy-sparse + git-fetch-with-cli +# git config --system insteadOf 走 gh-proxy +docker exec rustbuild cargo build --release + +# s6 基础镜像 +docker build -f /root/rustdesk/build/s6img/Dockerfile.local -t rustdesk-server-s6:1.1.17-forapi /root/rustdesk/build/s6img + +# 最终镜像(context 必须含 amd64/release) +docker build -f /root/rustdesk/build/fullimg/Dockerfile \ + --build-arg BUILDARCH=amd64 -t yangjieshao/rustdesk-api:full-s6-amd64 /root/rustdesk/build/apibuild + +# 验证 +docker run -d --name rustdesk-test -p 21114-21119:21114-21119 -p 21116:21116/udp \ + -e RELAY= -v :/data -v :/app/data \ + yangjieshao/rustdesk-api:full-s6-amd64 +curl -s http://127.0.0.1:21114/_admin/ -o /dev/null -w '%{http_code}\n' +``` +--- + +# 原始 README 内容 + +> 以下为 `lejianwen/rustdesk-api` 原始仓库的 README,本仓库在此基础上增加了 +> 自连拦截(A 远程连 A)API 侧支持。 + +
@@ -333,225 +630,3 @@ - -## 感谢你的支持!如果这个项目对你有帮助,请点个⭐️鼓励一下,谢谢! - -[lejianwen/rustdesk-server]: https://github.com/lejianwen/rustdesk-server - ---- - -# 自连拦截(A 远程连 A)API 侧改动 - -> 日期:2026-09-06 -> 关联文档:[lejianwen/rustdesk-server README](https://github.com/lejianwen/rustdesk-server) -> 说明:以下为自连拦截方案中涉及本仓库(rustdesk-api,Go)的代码改动,服务端(hbbs)改动见 [lejianwen/rustdesk-server README](https://github.com/lejianwen/rustdesk-server)。 - -## 改动清单 - -| 文件 | 改动 | -|---|---| -| `service/user.go` | 新增 `GetDeviceIdByToken(token)`:查 UserToken(校验 ExpiredAt)返回 DeviceId | -| `http/controller/api/internal.go` | 新增内部接口 `GET /api/internal/token-device?token=`:仅回环 IP + `X-Secret` 头必须等于 JWT 密钥,否则 403;查不到 404 | -| `http/router/api.go` | 注册路由(置于 RustAuth 中间件之前) | -| `lib/jwt/jwt.go` | GenerateToken 增加 jti(`ID: uuid.NewString()`),每次登录 token 唯一 | - -## 运行配置 - -- API 内部地址可用 `RUSTDESK_API_INTERNAL_URL` 覆盖,默认 `http://127.0.0.1:21114` -- hbbs 与 API 同容器时,`RUSTDESK_API_JWT_KEY` 双方共享(hbbs 的 jwt::SECRET 即 API 的 jwt.key) -- ⚠️ **部署必设 `RUSTDESK_API_JWT_KEY`**(任意字符串)。未设置时 hbbs 的 JWT 密钥为空, - token 校验和自连拦截**整体静默关闭**(fail-open 设计)。修改该值后客户端需退出重新登录。 - ---- - -# RustDesk 服务器合并 1.1.17 与 Docker 镜像构建记录 - -> 日期:2026-09-05 -> 目标:将官方 `rustdesk/rustdesk-server` 1.1.17 的更新合并到 `lejianwen/rustdesk-server`(forapi 分支,带账号 API 支持),并打包出可直接使用的完整 Docker 镜像。 - -## 1. 背景与仓库关系 - -| 仓库 | 说明 | 版本状态 | -|---|---|---| -| `/root/rustdesk/rustdesk/rustdesk-server` | 官方原版(只读,全程未改动) | 1.1.17(2026-08) | -| `/root/rustdesk/lejianwen/rustdesk-server` | 第三方修改版,`forapi` 分支持账号登录/API/JWT | 原基于 1.1.14(2025-09),本次合并到 1.1.17 | -| `/root/rustdesk/lejianwen/rustdesk-api` | Go 编写的 API 服务,`Dockerfile_full_s6` 打完整镜像 | 原样使用 | - -两个 rustdesk-server 仓库在官方 1.1.14(merge-base `c650217`)处分叉: -官方之后有 43 个提交(1.1.15 → 1.1.17),lejianwen 有 38 个自己的提交。 - -**官方 1.1.15~1.1.17 的主要改动**(合并的重点): - -- 安全:禁用 UDP 打洞反射/放大路径(`PunchHoleRequest`/`PunchHoleSent`/`LocalAddr` 只允许走 TCP/WS);中继 X-Real-IP 头欺骗风险注释;升级 mio 0.8.11、protobuf 3.7.2 -- 功能:`-b/--bind` 绑定指定 IP;控制台监听独立绑到 127.0.0.1;`punch-requests(pr)` 连接日志控制台命令 -- 修复:在线状态 `i32` 溢出(运行 24.9~49.7 天后误报在线);中继限速默认值 4Mb/s → 32Mb/s -- 依赖:`hbb_common` 子模块多次推进(最终 `69cea8d`)、新增 `flate2` - -## 2. 合并过程与冲突处理 - -执行 `git merge upstream/master`(在 lejianwen 仓库本地添加 `upstream` 指向官方目录)。 - -| 冲突文件 | 处理方式 | -|---|---| -| `src/rendezvous_server.rs` | 保留 fork 的 JWT/必须登录/KeyExchange/加密 Sink/API 特性,叠加上游全部修复(bind、console listener、UDP 禁用、i64、PUNCH_REQS、`punch-requests` 命令等)。共 4 处冲突块手动合并 | -| `src/main.rs` | 双方参数合并:上游的 `--bind` + DEPRECATED 标注,保留 fork 的 `--must-login` | -| `libs/hbb_common`(子模块) | 从 fork 的旧提交直接指到官方 1.1.17 用的 `69cea8d`。先确认 fork 用到的 `tcp::Encrypt` 接口在新版仍存在 | -| `Cargo.toml` / `Cargo.lock` | git 自动合并成功(1.1.17 + flate2 + 保留 jsonwebtoken/headers);lock 文件 cargo 构建后未再变动,说明自动合并结果一致 | -| `README*.md`、`.github/workflows/*`、`docker-compose.yml`、`docker/Dockerfile` | 取 fork 版本(文档是 API 版、CI 是 lejianwen 自己的发布流程) | - -**合并中发现的注意点**: - -- 上游把 `handle_udp` 里的三个打洞消息改成"故意不支持",fork 的 `handle_udp_punch_hole_request` 变成死代码(仅编译警告),保留不影响行为;webclient 打洞走 TCP/WS 路径不受影响 -- fork 侧 `check_cmd` 帮助文本比上游多一行 `must-login`,合并 `punch-requests` 时要同步把 format! 占位符从 7 个加到 8 个,**否则编译报 `argument never used`**(本次实际踩过) -- 上游的 `peers_online_state` 是内联代码,fork 抽成了 helper(`handle_online_request` 复用),合并时保留 helper 并把 `as i32` 改 `as i64` - -## 3. 构建环境问题与解决(按时间顺序) - -本机只有 `docker` 和 `gcc`,没有 rust/go 工具链,全部编译在容器中进行。 - -### 3.1 容器内访问 GitHub 失败 - -**现象**:容器内 `cargo build` 拉取 git 依赖(`confy` 等 3 个来自 github.com)时 `SSL routines::unexpected eof` / 连接超时,重试 10 次仍失败;直连 `git ls-remote github.com` 间歇性可用。 -**原因**:到 GitHub 的连接被间歇性重置(典型网络环境问题),宿主机偶尔能通但容器内不稳定。 -**解决**: -- 测试可用的加速代理:`gh-proxy.com`、`ghproxy.net`(`ghfast.top` 返回 403) -- 容器内配置系统级改写(注意写 `/etc/gitconfig`,`--global` 在某些 exec 环境不可靠): - -```sh -git config --system url.'https://gh-proxy.com/https://github.com/'.insteadOf 'https://github.com/' -``` - -- s6-overlay 等构建期从 GitHub 下载的文件改为**宿主机预下载**(走代理)后 `COPY` 进镜像,见 3.6 - -### 3.2 Alpine apk 源问题 - -**现象**:`rust:alpine`(Alpine 3.24.1)内 `apk add` 卡住 10 分钟无进展;换清华源后 `HTTP 403: Forbidden`。 -**原因**:官方 CDN 在当前网络环境慢;清华源对 v3.24 索引 403。 -**解决**:使用阿里云源(与镜像版本匹配的 v3.24): - -```sh -printf 'https://mirrors.aliyun.com/alpine/v3.24/main\nhttps://mirrors.aliyun.com/alpine/v3.24/community\n' > /etc/apk/repositories -``` - -注意先确认镜像的 Alpine 版本(`cat /etc/alpine-release`),不要照搬别的版本号。 - -### 3.3 挂载 CARGO_HOME 盖住了 Rust 工具链 - -**现象**:容器内 `cargo: not found`。 -**原因**:把宿主目录挂载到 `/usr/local/cargo`(想持久化缓存),而 `rust:alpine` 的 cargo/rustc 就装在这个路径里,挂载把它们全遮住了。 -**解决**:不挂载该路径,cargo 配置直接写容器内 `/usr/local/cargo/config.toml`;既然用持久化容器(`docker run -d ... sleep infinity` + `docker exec`),缓存本来就不会丢。 - -### 3.4 cargo 的 git-fetch-with-cli 需要 git 二进制 - -**现象**:`could not execute process 'git fetch ...' (never executed) - No such file or directory`。 -**原因**:`rust:alpine` 不带 git,而 config 里设了 `git-fetch-with-cli = true`。 -**解决**:`apk add git`。(该模式值得保留:CLI git 才能吃 `insteadOf` 改写,cargo 内置 libgit2 不认。) - -### 3.5 Go 构建:go.sum 缺条目 - -**现象**:`missing go.sum entry for module providing package github.com/spf13/cobra`。 -**原因**:仓库快照的 `go.mod` 与 `go.sum` 不同步(上游代码在 go.mod 里加了 cobra 但没提交 go.sum 条目)。 -**解决**:把仓库**复制到临时目录**(不动原仓库)执行 `go mod tidy` 后再 `go build`。CGO 静态编译命令: - -```sh -CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=gcc CGO_LDFLAGS='-static' \ - go build -ldflags '-s -w' -o ./amd64/release/apimain ./cmd/apimain.go -cp -ar resources docs conf amd64/release/ && mkdir -p amd64/release/{data,runtime} -``` - -(alpine 容器里 gcc 本身就是 musl-gcc,等价于官方 CI 的 musl-tools 静态构建;产物 `file` 确认 `statically linked`。) - -### 3.6 docker build 阶段无法访问 GitHub(s6-overlay) - -**现象**:官方 `docker/Dockerfile` 用 `ADD https://github.com/just-containers/s6-overlay/releases/download/...` 构建期联网下载。 -**解决**:宿主机预下载(`curl -L https://gh-proxy.com/https://github.com/...`),复制官方 Dockerfile 后把两条 `ADD` 改成本地 `COPY`(不改动官方仓库)。下载物: -- `s6-overlay-noarch.tar.xz` -- `s6-overlay-x86_64.tar.xz` -- 版本 v3.2.0.0(与 Dockerfile 内 `S6_OVERLAY_VERSION` 一致) - -### 3.7 没有 buildx,无法用 --build-context 覆盖 FROM - -**现象**:本机 docker 26.1.4 无 buildx 插件,`docker build --build-context` 不可用。 -**解决**:复制 `Dockerfile_full_s6` 到构建目录,sed 把首行 `FROM rustdesk/rustdesk-server-s6:latest AS server` 改为本地基础镜像名,构建时 `-f` 指向副本、context 指向含 `amd64/release` 的目录。 - -### 3.8 admin 面板 404:resources/admin 不在仓库里 - -**现象**:镜像跑起来后 `/_admin` 返回 404,release 目录里没有 `admin` 静态文件。 -**原因**:admin 前端来自独立仓库 `lejianwen/rustdesk-api-web`,官方 CI 先 checkout 该仓库、`npm run build` 后把 `dist/*` 拷进 `resources/admin/`,这一步在本地打包时缺失。 -**解决**: - -```sh -git clone --depth 1 -b master https://gh-proxy.com/https://github.com/lejianwen/rustdesk-api-web -docker run --rm -v $PWD:/srv -w /srv node:20-alpine sh -c \ - "npm config set registry https://registry.npmmirror.com && npm install && npm run build" -cp -a rustdesk-api-web/dist/. /resources/admin/ -``` - -Go 二进制从磁盘读 `resources` 目录(不内嵌),所以**无需重编 Go**,重打镜像层即可。 - -### 3.9 杂项 - -- **pkill 自杀**:`pkill -f cargo` 会匹配到承载它的 `sh -c "... pkill -f cargo ..."` 自身,返回 143。用 `pkill -f '[c]argo'` 括号模式规避 -- **管道吞退出码**:`cargo build | tail` 拿到的是 tail 的退出码;busybox ash 不支持 bash 的 `PIPESTATUS`。判断成功与否要看日志里的 `Finished`/`error` 行,或不用管道 -- **镜像标签冲突**:为避免与作者发布的 `lejianwen/rustdesk-api:full-s6-amd64` 撞名,最终镜像按需求改为 `yangjieshao/rustdesk-api:full-s6-amd64`(仅本地,未推送) - -## 4. 产物与验证结果 - -### 4.1 镜像 - -| 镜像 | 说明 | -|---|---| -| `yangjieshao/rustdesk-api:full-s6-amd64` | 最终完整镜像(hbbs + hbbr + rustdesk-utils + apimain + admin 前端,s6 托管) | -| `rustdesk-server-s6:1.1.17-forapi` | 本地自建的 s6 基础镜像(对应官方 `rustdesk/rustdesk-server-s6`,内含合并后的 1.1.17 二进制,musl 静态链接) | - -服务器二进制:`hbbs`/`hbbr`/`rustdesk-utils`,`file` 确认 `ELF x86-64 static-pie linked`。 - -### 4.2 实测结论 - -- 21114(API)/21115(NAT)/21116(tcp+udp hbbs)/21117(hbbr)/21118(ws)/21119(ws relay) 全部监听 -- `POST /api/login` 返回业务校验 JSON(接口正常) -- `/_admin/` 返回 200(admin 面板正常) -- s6 healthcheck:3 个服务(hbbs、hbbr、api)全部 up -- `/data` 下正确生成 `id_ed25519(.pub)` 与 `db_v2.sqlite3` -- 日志出现 `LIMIT_SPEED: 32Mb/s`、`Listening on websocket :21119`,确认 1.1.17 代码与 fork 的 relay WS 支持同时生效 - -### 4.3 已知无害现象 - -- 启动日志 `ERROR ... Failed to get machine uid: No such file or directory`:busybox/alpine 容器内没有 dbus machine-id 所致,官方镜像同样存在,hbbs 自动生成 ID 兜底,不影响功能 -- 对 21118/21119 用普通 curl 探测返回 000/连接重置:这两个端口只接受 WebSocket 握手,非 ws 请求被断开是预期行为 - -## 5. 注意事项 - -1. **远端未动**:全程未 push、未登录任何账号。本地仅有两个 git 操作: - - forapi 分支上一条本地合并提交(含格式串修复,已 amend 进合并提交) - - 备份分支 `forapi-backup-pre-merge`(合并前状态,`git reset --hard forapi-backup-pre-merge` 可整体还原) -2. **官方仓库 `/root/rustdesk/rustdesk/rustdesk-server` 全程只读**;镜像构建上下文是复制到 `/root/rustdesk/build/s6img` 后再改的 -3. **代理配置时效性**:`gh-proxy.com`/`rsproxy.cn`/阿里云 apk 源均为当下可用的国内加速,失效时按 3.1/3.2/3.8 的思路换源 -4. **目录占用**:`rustdesk-server/target/` 是编译缓存(数 GB,不需要可删);`/root/cargo-home` 是一次失败尝试的残留(可删);构建相关都在 `/root/rustdesk/build/`(`s6img/`、`fullimg/`、`apibuild/`、`rustdesk-api-web/`、`testdata/`) -5. **测试容器**:`rustdesk-test` 还在运行(测试数据在 `build/testdata`,admin 初始密码 `S2qAgqSw`),`docker rm -f rustdesk-test` 清理 -6. **下次升级的路径**:本次做法可复用——`git fetch upstream && git merge upstream/master`,重点冲突仍会集中在 `src/rendezvous_server.rs`(fork 的 API 特性与上游安全修复叠加);hbb_common 子模块直接指到上游对应提交;编译用同样的容器方案 - -## 6. 常用命令备忘 - -```sh -# 服务器编译(持久容器方案) -docker run -d --name rustbuild --network host \ - -v /root/rustdesk/lejianwen/rustdesk-server:/srv -w /srv rust:alpine sleep infinity -# 容器内:换阿里云 apk 源 → apk add build-base pkgconf openssl-dev openssl-libs-static git -# /usr/local/cargo/config.toml 配置 rsproxy-sparse + git-fetch-with-cli -# git config --system insteadOf 走 gh-proxy -docker exec rustbuild cargo build --release - -# s6 基础镜像 -docker build -f /root/rustdesk/build/s6img/Dockerfile.local -t rustdesk-server-s6:1.1.17-forapi /root/rustdesk/build/s6img - -# 最终镜像(context 必须含 amd64/release) -docker build -f /root/rustdesk/build/fullimg/Dockerfile \ - --build-arg BUILDARCH=amd64 -t yangjieshao/rustdesk-api:full-s6-amd64 /root/rustdesk/build/apibuild - -# 验证 -docker run -d --name rustdesk-test -p 21114-21119:21114-21119 -p 21116:21116/udp \ - -e RELAY= -v :/data -v :/app/data \ - yangjieshao/rustdesk-api:full-s6-amd64 -curl -s http://127.0.0.1:21114/_admin/ -o /dev/null -w '%{http_code}\n' -``` \ No newline at end of file