# scrcpy-web-rtc-tos **Repository Path**: chenshaogui/scrcpy-web-rtc-tos ## Basic Information - **Project Name**: scrcpy-web-rtc-tos - **Description**: 铁威马(TerraMaster)TOS 系统专属的 Scrcpy WebRTC 网页投屏方案。针对 TOS 的 Docker 环境与系统服务进行深度适配,提供一键部署脚本、Nginx 反向代理配置与 SSL 证书集成,支持 TOS 后台管理与开机自启,开箱即用。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-25 - **Last Updated**: 2026-11-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 云手机助手(Scrcpy WebRTC)—— 铁威马 TOS 7 Deb 应用 在任何浏览器中 **查看并控制 Android 设备 / 云手机** 的自托管服务:内置 [scrcpy-webrtc](https://github.com/tiann/scrcpy-webrtc) 引擎(基于 [ya-webadb](https://github.com/yume-chan/ya-webadb)),把设备硬件 H.264/Opus **不转码** 直接经 WebRTC(P2P)或 WebSocket(兜底)送到浏览器,支持画面、音频、 触控、剪贴板双向同步与设备 Shell。 本项目是 **`scrcpy-web-rtc`(Node.js 版)向 铁威马 TOS 7 Deb 应用 的移植**:后端逻辑与 前端界面 **与原应用完全一致**,仅把 **运行时分发、打包格式、服务管理、访问方式** 适配为 TOS 7 规范,并 **随包内置静态 Node 运行时**(TOS 不自带新版 Node)。 ## 应用子类型与选型 按《TOS 7 Application Development Guide》,本应用为 **Deb / WebUI Internal Open(TOS 桌面 iframe 内嵌)**: - 后端是本地可执行 HTTP + WebSocket 服务,**只监听 Unix Socket `/var/api/scrcpy-web-rtc-tos.sock`(0660)**, 不开放任何 TCP 端口(官方 §8.7.1); - `config.ini` 用 `type: "iframe"` + `path: /scrcpy-web-rtc-tos/`(§8.4.1),前端由平台从 `webui.bz2` 解包托管,在**已登录 TOS 桌面**的 iframe 中打开; - 前端所有 REST API 与 WebSocket 请求走官方平台代理 `/v2/proxy/scrcpy-web-rtc-tos/`(§8.9), 由平台 nginx 转发到该 Unix Socket; - **访问控制(S4)**:应用只在已登录的 TOS 桌面内打开。后端按 §8.10 校验平台会话 `TMSESSNAME` 与 `X-Csrf-Token` 请求头,失效返回 401、前端跳转 TOS 登录页。 **不设自定义口令、不签发自己的 Cookie、不投放自定义 nginx 片段**; - 服务由 **systemd**(`scrcpy-web-rtc-tos.service`)管理,App Center 通过它显示运行状态与启停。 > **为什么用 Internal Open**:审核项 [S4] 要求服务具备访问控制。TOS 的 External Open > 模板只是纯转发、不校验登录态,且平台会给**未登录访客**也下发 `TMSESSNAME`, > 因此应用自行检查 cookie 并不能构成鉴权。官方唯一文档化的复用登录态机制就是 > iframe + Unix Socket + 平台代理,故 1.1.0 起按此实现。 > **§8.10 的实测适配**:TOS 7 下发的 `TMSESSNAME` 带 `HttpOnly`,前端 JS 读不到, > 无法按官方示例手工拼接自定义 `Cookie` 头;但浏览器会在同源请求中自动携带它。 > 因此前端改为:请求加 `X-Csrf-Token` 头(可读的 `X-Csrf-Token` cookie 值)并置 > `credentials: "include"`,后端校验 cookie 中 `TMSESSNAME` + `X-Csrf-Token` 且 > 与头一致;WebSocket 握手无法设置自定义头,按同源自动携带的两个平台 cookie 校验。 ## 运行时文件清单(§12.9.6,Required) | 路径 | 用途 | 格式 | 创建时机 | 增长上限 | 生命周期 | | --- | --- | --- | --- | --- | --- | | `/usr/local/scrcpy-web-rtc-tos/config.ini` | 应用元数据 | JSON | 安装 | 固定 | 随包,`dpkg` 删除 | | `/usr/local/scrcpy-web-rtc-tos/webui.bz2` | 前端归档(index.html + privacy.html) | bzip2 tar | 安装 | 固定 | 随包,`dpkg` 删除 | | `/usr/local/scrcpy-web-rtc-tos/index.html`、`privacy.html` | 解包后的前端页面 | HTML | `postinst` 解包 | 固定 | 卸载删除 | | `/usr/local/scrcpy-web-rtc-tos/server.cjs`、`server.bin` | 后端 bundle 与 scrcpy 引擎 | JS / 二进制 | 安装 | 固定 | 随包 | | `/usr/local/scrcpy-web-rtc-tos/runtime/bin/node` | 内置静态 Node 运行时 | ELF | 安装 | 固定 | 随包 | | `/usr/local/scrcpy-web-rtc-tos/bin/scrcpy-web-rtc-tos`、`nginx-proxy.py` | 启动器 / 旧版升级清理 | bash / Python | 安装 | 固定 | 随包 | | `/usr/local/scrcpy-web-rtc-tos/data/devices.json` | 用户手动添加的设备列表 | JSON | 首次添加设备 | 设备数受限,通常 < 64 KB | 升级保留,卸载删除 | | `/usr/local/scrcpy-web-rtc-tos/data/adbkey`、`adbkey.pub` | adb RSA-2048 凭据 | PEM | 首次连接设备 | 固定 ~2 KB | 升级保留,卸载删除 | | `/usr/local/scrcpy-web-rtc-tos/logs/` | 预留日志目录 | 目录 | `postinst` | 不落盘(stdout → journald) | 升级保留,卸载删除 | | `/var/api/scrcpy-web-rtc-tos.sock` | 平台代理入口(0660) | Unix Socket | 服务启动 | 单文件 | 服务停止/卸载删除 | 卸载时 `postrm` 在 `remove|purge` 下清理 socket、`data/`、`logs/` 与解包页面; 删除前判断 `data/` 是否为共享卷软链(§12.9.7),是则只删链接本身。升级路径保留用户数据。 ## 设备接入:默认「直连模式」(无需 NAS 装 adb) scrcpy-webrtc 有两种连设备方式,本包 **默认直连模式**(`ADB_DIRECT=1` + 启动器加 `--direct-adb`): - **直连模式(推荐)**:用 ya-webadb 的 `AdbDaemonTransport` 通过 TCP 直连设备的 `adbd`(如 **redroid 云手机**、已 `adb tcpip` 的无线 adb 设备)。**NAS 上无需安装 adb 二进制**,在页面「ADB 连接」 里填 `IP:5555` 即可 `adb connect`。这也是 ARM 机型上的唯一可靠方式。 - **adb-server 模式**:把 `scrcpy-web-rtc-tos.env` 里 `ADB_DIRECT=0`,并确保 `SCRY_ADB_HOST:SCRY_ADB_PORT`(默认 `127.0.0.1:5037`)上有 adb-server。 ## 目录结构 ``` scrcpy-web-rtc-tos/ ├── src/ 后端源码(唯一事实来源,改这里) │ ├── server.mjs HTTP+WebSocket 服务、平台会话校验、REST API │ ├── webrtc.mjs WebRTC 会话(werift) │ ├── signal.mjs 信令(本包用 --no-signal,走 WS 本地信令) │ ├── deviceStore.mjs 直连模式的设备簿记(devices.json / adbkey) │ ├── adbTcp.mjs 纯 TCP 的 ADB 帧协议 + RSA 凭据存储 │ └── ui/index.html 前端(内联整段应用,含官方认证头与 401 跳转) ├── app/ 打包用运行期产物(build.sh 从此取用;可由 src 再生) │ ├── server.cjs src/server.mjs 的 esbuild 单文件 CJS bundle │ ├── server.bin scrcpy v3.1 服务二进制(Apache-2.0) │ ├── package.json {version} │ └── public/{index,privacy}.html ├── scripts/bundle.mjs esbuild 打包:src/server.mjs → app/server.cjs ├── config.ini 应用核心元信息(type: iframe) ├── scrcpy-web-rtc-tos.lang 26 语言文案 ├── scrcpy-web-rtc-tos.env systemd EnvironmentFile(SCRY_VAR_DIR / SCRY_SOCKET_PATH / ADB_DIRECT) ├── bin/scrcpy-web-rtc-tos 后端启动器(创建并清理 Unix Socket) ├── bin/nginx-proxy.py 仅用于从旧版 External Open 升级时撤销历史 include ├── images/icons/scrcpy-web-rtc-tos.svg ├── init.d/scrcpy-web-rtc-tos.service systemd 单元(ProtectSystem=strict + ReadWritePaths /var/api) ├── DEBIAN/{control,postinst,prerm,postrm} ├── PRIVACY.md NETWORK_FLOW.md BINARIES.md VULNERABILITY_SCAN.md ├── build.sh validate.sh └── dist/ 打包产物(.gitignore 已忽略) ``` ## 环境要求 | 环节 | 依赖 | |---|---| | 打包(build.sh) | Linux / WSL / NAS 终端:`dpkg-deb`、`curl`、`tar(xz)`、`bzip2`、`sed` | | 再生 bundle | Node ≥ 18 + `npm install`(devDep `esbuild`);仅改 `src/` 后需要 | | 运行(TNAS) | TOS 7.0 及以上;systemd;**无需**系统 Node/adb;平台需提供 `/var/api` | ## 打包 ```bash ./build.sh x86_64 # 产物:dist/scrcpy-web-rtc-tos_1.1.0_amd64.deb(内置 node linux-x64) ./build.sh aarch64 # 产物:dist/scrcpy-web-rtc-tos_1.1.0_arm64.deb(内置 node linux-arm64) ``` `build.sh` 流程:运行 `validate.sh` → 校验 `config.ini` 为 `type: iframe` → 取 `app/server.cjs` (缺失则 `npm install && npm run bundle` 现场生成)→ 组装 payload 到原生 Linux tmpdir 的 staging → 按架构 **下载官方静态 Node**(`NODE_VERSION`,默认 22.14.0)植入 `runtime/bin/node` → 由 `app/public/` 生成 `webui.bz2` → 修正 `config.ini` 的 `platform` 与 `control` 的 `Architecture` → 去 BOM、统一 LF → `dpkg-deb --build` 输出到 `dist/` → 自检包内文件且**不得含** nginx 片段或口令文件。 > ⚠ `config.ini`、`*.lang`、`images/` 必须位于 deb 内 `usr/local/scrcpy-web-rtc-tos/` 下 > (平台解析器从 `data.tar` 该路径读取),不能放在 deb 根目录。 ## 修改源码后重新生成 bundle ```bash npm install npm run bundle # src/server.mjs → app/server.cjs # 本地自测(不设 SCRY_SOCKET_PATH 时回退到 TCP 端口): node app/server.cjs --port 18899 --no-page --no-signal --no-qr --direct-adb # 浏览器打开 http://127.0.0.1:18899/ ``` ## 安装部署(TOS 7) 1. 应用中心 → 本地安装 / 上传,选择 `scrcpy-web-rtc-tos_1.1.0_.deb`。平台自动创建运行用户, `postinst` 会创建 `data/`、`logs/`、`/var/api`,清理旧版 External Open 遗留的 nginx 片段与口令文件, 解包 `webui.bz2`,并 enable+restart 服务。 2. 在 **TOS 桌面** 点应用图标,页面以 iframe 打开;未登录 TOS 时无法进入桌面,也不会到达本应用。 会话失效时后端返回 401,前端自动跳转 TOS 登录页。 3. 在页面「ADB 连接」填写设备的 `IP:端口`,点连接 → 选中设备 → 开始镜像。 4. 画质选择器 Auto/流畅/高清、音频、剪贴板、Shell 均在页面内操作。 > 从 1.1.0 首版(External Open)升级时,`postinst` 会删除 `/etc/nginx/conf.d/scrcpy-web-rtc-tos.conf`、 > `data/access.token`、`data/access.txt` 与 `logs/nginx-reload.log`。若平台提示 nginx 配置异常, > 执行 `nginx -t` 并重启 nginx(TOS 上 nginx 命令不要加 sudo)。 ## 关键行为与安全说明 - **WebRTC 媒体走 P2P(UDP)**;控制/信令与 WS 兜底视频走 `/api/stream`。默认 **不联系任何公网 STUN / TURN / 信令 broker / CDN**;逐项说明见 `NETWORK_FLOW.md`。 - **健康检查**:`GET /health`(不鉴权,返回 `{status,version}`),供平台探活。 - **访问控制(官方 §8.10)**:平台代理转发 `TMSESSNAME`(HttpOnly,浏览器自动携带)与 `X-Csrf-Token`;后端要求两者同时存在且 `X-Csrf-Token` 头值与 cookie 一致,否则 401。 WebSocket 握手无法设置自定义头,按同源自动携带的两个平台 cookie 校验。 预检 `OPTIONS` 仅回显 TOS 桌面来源,不使用通配 CORS。 - **运行数据**:`devices.json` 与 `adbkey` 写入 `/usr/local/scrcpy-web-rtc-tos/data`(systemd `ReadWritePaths` 放行;启动器把 `TRIM_PKGVAR`/`HOME` 指到该目录)。升级保留; **普通卸载与 `dpkg --purge` 都会清理**(F11)。用户可通过 `GET /api/data/export` 导出、 `GET /api/data/delete` 删除,详见 `PRIVACY.md`。 - **合规材料**:`PRIVACY.md`(含应用内 `privacy.html`)、`NETWORK_FLOW.md`、`BINARIES.md`、 `VULNERABILITY_SCAN.md` 均随包安装。 ## TOS 桌面窗口按钮适配 iframe 模式下,窗口标题栏的**最小化 / 最大化 / 关闭**按钮由 TOS 桌面渲染,应用不绘制、也无法改变其样式;`config.ini` 的 `maxmin` / `resize` 字段控制前两个按钮是否出现(官方 §8.4.2,默认 `false`;官方 Deb 单包模板默认为 `true`,本包沿用)。关闭按钮始终存在。 应用侧按以下方式适配这些按钮引起的变化: | 平台动作 | 应用响应 | | --- | --- | | 最大化 / 还原 / 拖动调整 | `html,body` 用 `100dvh`,视频 `object-fit: contain`;前端已注册 `window.resize`、视频容器的 `ResizeObserver` 与 `fullscreenchange`,会重算画面缩放、旋转与 xterm `fit()`。外层再对 `documentElement` 做尺寸去重观察,仅在尺寸真正变化时补发一次 `resize` | | 最小化 / 切到后台 | `visibilitychange` 隐藏时暂停 `