Linux、FreeBSD、NetBSD、Windows、macOS 与 Android 设备
Linux(glibc / musl)、FreeBSD、NetBSD、Windows、macOS 与 Android 设备复用 remote-agent、Exchange、remote-cli 和移动维运用户端。设备端与 CLI 使用通用名称;环境变量统一为 REMOTE_*。
一条命令安装(Windows / Linux / macOS / FreeBSD / NetBSD)
官网脚本会按系统下载校验包,把二进位档放到 ~/.local/bin,Agent 再调用 remote-agent install 注册登录自启动:
按用途选择以下命令,分别复制执行。
安装 Agent(被管理设备)
curl -fsSL https://remote.ivjn.us/install.sh | bash -s -- agent
安装 CLI(操作端)
curl -fsSL https://remote.ivjn.us/install.sh | bash -s -- cli
Windows 在 Git Bash 中运行。Agent 把凭证写在管道后面:bash -s -- agent --device-id NAME --token TOKEN(或已有 config.json / REMOTE_CONFIG)。写在 curl 前面的环境变量到不了 bash。Linux 系统级 systemd 可加 --system。脚本会自动区分 glibc 与 musl(Alpine 等),Linux arm64(aarch64)同样支持;可用 REMOTE_LIBC=musl 强制选择 musl 包。FreeBSD / NetBSD:普通用户安装保留在 ~/.local/bin;使用 sudo remote-agent install --system --config CONFIG 注册 rc.d。桌面控制需要带 python3 和 xdotool 的 X11 用户会话。Android 与 OpenWrt 使用各自安装包。
命令与服务
设备端与 CLI 命令分别为 remote-agent 和 remote-cli。remote-cli 默认写入 remote-cli/config.toml,显式设置 REMOTE_CLI_CONFIG 优先。
OpenWrt 包、UCI 文件和 init 服务均为 remote-agent,状态文件为 /var/run/remote-agent.state,配置文件为 /etc/config/remote-agent。LuCI 插件 luci-app-remote 依赖该包。REMOTE_AGENT_SRC 用于指定构建源码路径。
| 设备 | 命令运行 | 交互终端 | 常驻方式 | ubus |
|---|---|---|---|---|
| Linux | /bin/sh -c |
Unix PTY | systemd 或前景进程 | 仅 OpenWrt |
| Linux musl(Alpine 3.x 等),x64 / arm64 | /bin/sh -c |
Unix PTY | 前景进程或自写 OpenRC 服务(无 systemd) | 不支持 |
| FreeBSD 14+,amd64 / arm64 | /bin/sh -c |
Unix PTY | rc.d(root/系统);前台(用户) | 不支持 |
| NetBSD 10+,amd64 | /bin/sh -c |
Unix PTY | rc.d(root/系统);前台(用户) | 不支持 |
| Windows 10 1809+/11、Server 2019+ | Windows PowerShell 5.1 | ConPTY + PowerShell | 登录调度工作或前景进程 | 不支持 |
| macOS 11+,Apple Silicon / Intel | /bin/sh -c |
Unix PTY | launchd LaunchAgent 或前景进程 | 不支持 |
| Android 8.0+,arm64 / x86_64 | /system/bin/sh -c |
Unix PTY | 独立 APK 前景服务 | 不支持 |
通用设置
先通过 remote-cli device add <id> 登记设备、取得设备权杖。Linux/Windows 创建仅当前运行账号可读的 config.json:
{
"server": "wss://mgmt.example.com",
"device_id": "my-device",
"token": "REPLACE_WITH_DEVICE_TOKEN"
}
remote-agent --config /path/to/config.json
优先级为命令行/REMOTE_SERVER、REMOTE_DEVICE_ID、REMOTE_TOKEN 环境变量,再到 JSON,最后仅在 OpenWrt 回退 UCI。JSON 还可设置 state_file;命令行 --state-file / REMOTE_STATE_FILE 可覆盖。未指定状态文件时,OpenWrt 仍使用 /var/run/remote-agent.state;其它平台依次使用 LOCALAPPDATA、XDG_STATE_HOME、$HOME/.local/state 下的 remote-agent/agent.state,无用户目录时回退临时目录。多个实例应各用独立状态路径。
正式连接使用 wss://。示例中的 ws://127.0.0.1 仅供本地验证。
Agent 链路通知
JSON 可设置 "notify_urls": ["https://example.invalid/notify"],或使用 remote-agent --notify-url https://example.invalid/notify(可重复);环境变量 REMOTE_NOTIFY_URL 接受逗号分隔的地址。非空命令行/环境变量列表替换 JSON 设置;否则使用 JSON,最后仅在 OpenWrt 回退到逗号分隔的 UCI remote-agent.main.notify_url。所有来源均省略或为空时禁用 POST。
注册成功后发送 agent.connected,已创建的会话结束时发送一次 agent.disconnected,明确拒绝注册时只发送 agent.rejected;注册前的网络失败不通知。POST 使用与 Exchange Web Push 相同的 JSON 对象(title、body、url、event、device_id、occurred_at),但描述的是 agent 链路而非设备在线状态:event 前缀为 agent. 而非 device.,url 为 ""(没有 Web Push 控制台路径),不发送 tag。断开和拒绝的正文包含简短的安全原因摘要,不包含设备权杖;时间为 RFC3339 UTC。
通知在背景发送,失败只记录事件名,不记录目标地址,不阻止 agent 重连。重试策略类似 Exchange Web Push:最多尝试 5 次,网络错误和 HTTP 408/429/5xx 可重试,404/410 和其它不可重试 4xx 丢弃;优先使用整数秒 Retry-After,否则指数退避从 5 秒开始,延迟限制为 1–900 秒。仅接受 HTTP/HTTPS,不跟随重定向;不要在 URL 中放用户名、密码或其它凭据,含用户名/密码的地址会被拒绝。
出站代理与连接保活
无接口(headless)与 --ui 都通过同一个 client::run 连接 Exchange,WebSocket 读取 agent 进程继承的代理环境变量:依次取首个非空的 HTTPS_PROXY / https_proxy、ALL_PROXY / all_proxy、HTTP_PROXY / http_proxy;NO_PROXY / no_proxy 匹配的主机绕过代理。remote-cli 的 WebSocket 使用相同规则(#36/#37、#39)。登录自启动或背景服务需要在其运行环境中设置这些变量,仅在另一个终端里设置不会传给已启动的进程。
设备 WSS 经代理时先创建 HTTP CONNECT 隧道,再用 rustls 连接目标;TLS ClientHello 的 ALPN 仅声明 http/1.1,与 CLI 一致。协商成 h2 会破坏当前 HTTP/1.1 WebSocket Upgrade 路径;Clash 一类代理下卡在连接中时,应检查代理是否支持 CONNECT,并使用包含 #39/#42 修复的建置。设备与 CLI 的 WebSocket 路径均不支持 SOCKS,请使用 HTTP CONNECT 代理(例如 HTTPS_PROXY=http://127.0.0.1:7890)。
直连或 CONNECT 完成后,agent 激活 TCP_NODELAY 和空闲 30 秒后的 TCP keepalive;每次 JSON 心跳还会发送 WebSocket Ping,收到 Ping 后显式 flush 自动排队的 Pong。连接与注册应答等待各限时 45 秒,发送及注册后的 Pong flush 限时 30 秒,详见 保活与逾时(#42)。
启用 desktop-ui 的 Windows 构建使用多线程 Tokio runtime,让 axum、本机托盘与设备 WebSocket 并行调度;同一构建以 headless 方式运行也用多线程。其他平台启用该特性时,本机 UI(显式 --ui 或无参数启动)同样使用多线程。未启用该特性的构建,以及其他平台显式指定配置的 headless 运行,使用 current_thread,适合小设备。
Linux / Windows / macOS 设置接口
默认构建启用 desktop-ui,桌面发布包必须保留本机配置界面。仅无界面服务器或嵌入式构建使用 --no-default-features。已连接时隐藏管理服务器、设备 ID 和设备令牌输入区;停止远程访问后重新显示,连接状态、LAN 设置和登录自启动保持可见。
cargo build --locked -p agent --release
# Linux / macOS
./target/release/remote-agent --ui
# Windows
.\target\release\remote-agent.exe --ui
在 Linux / Windows / macOS 上,若以无连接参数方式启动(例如双击 remote-agent.exe),且激活了 desktop-ui:缺少可用设置时会写入缺省 config.json(仅预填缺省 Exchange,device_id/token 留空)并打开浏览器设置页;已设置时 Windows 仍走托盘(不再强行打开浏览器),Linux/macOS 打印本机 URL(macOS 另有菜单栏入口)。未激活 desktop-ui 的交互式裸启动会创建缺省设置文件并打印路径后退出,避免只显示 Missing device_id。OpenWrt / systemd / 调度工作等仍应使用 --config、UCI 或 REMOTE_*。
无参数启动与显式 --ui 使用相同规则:仅在配置缺少非空 device_id/token 时自动打开默认浏览器;已有配置时在后台连接,不会因登录自启动再次弹出浏览器。Windows 保留托盘,可手动打开配置页;Linux/macOS 使用终端打印的本机 URL。--ui --no-open 始终禁止自动打开,包括首次配置。--ui-port 8765 可指定端口,默认随机选择空闲端口,始终只监听 127.0.0.1。UI 资源全部内嵌,不访问外部字体或 CDN。
Windows 托盘和 macOS 菜单栏均提供配置页、屏保设置和任务管理入口,也可操作剪贴板;任务管理会直接打开本机任务面板。Linux 使用浏览器配置页。
先用 remote-cli device add <id> 取得设备 ID 与权杖,再在 Connection settings 填写服务器端、设备 ID 和设备权杖:
- Save only:保存设置,不连接服务器。
- Save and connect:保存并连接;运行中修改设置时会断开旧连接,再用新设置连接。
- Stop remote access:断开连接、取消当前远程运行和终端会话;设置保留。
- 状态栏显示注册成功后的 Connected、连接中、重连中或已停止,并显示最近的连接错误。
开始连接时,UI 会先把 Connecting 写入自己的状态文件,再启动 client::run 任务,避免页面停留在状态文件尚不存在时的默认值(#42)。
保存后的权杖不会回传网页,留空表示保留原权杖。设置使用临时文件原子替换,Linux 权限为 0600,Windows ACL 仅授权当前用户。设置文件 JSON 损坏或不可读时接口提示修复,不会覆盖原文件。同一个设置文件只能由一个 UI 进程打开。
缺省设置路径为 Linux/macOS 的 $XDG_CONFIG_HOME/remote-agent/config.json(未设置时 ~/.config/remote-agent/config.json),Windows 的 %LOCALAPPDATA%\remote-agent\config.json。--ui --config <path> 或 REMOTE_CONFIG 可指定现有 JSON 文件。UI 不接受 --server、--device-id、--token、--state-file 及对应环境变量覆盖,避免保存值和实际连接不一致。已有 JSON 中的 state_file 同样用于 UI;未指定时使用与 headless 一致的持久默认路径,以保留密封身份和信任配置。
设置页提供 Start at login / 登录自启动 开关(GET /api/status 的 autostart: { supported, enabled, detail },POST /api/autostart 传 { "enabled": true|false })。无需提权:
- macOS:安装/移除
~/Library/LaunchAgents/us.ivjn.remote-agent.plist(模板与install/macos/install.sh相同),launchctl bootstrap/bootout;二进位档优先拷贝到~/.local/bin/remote-agent,设置缺省~/.config/remote-agent/config.json(已存在则保留)。 - Windows:注册/注销当前用户调度工作
RemoteAgent(AtLogOn),逻辑对齐install/windows/install.ps1,安装目录%LOCALAPPDATA%\remote-agent。 - Linux:安装/移除用户 systemd 单元(
install/linux/remote-agent.service),systemctl --user enable --now/disable。
激活时会在需要时把当前可运行文件拷贝到约定安装路径,并保留已有 config.json;响应与日志不打印权杖。UI 管理的仍是当前进程;激活自启动后,退出 UI 时由 LaunchAgent / 调度工作 / systemd 在下次登录拉起背景 agent。
关闭浏览器标签页不会停止 Agent,退出 Agent 进程会停止连接。已有设备配置时 UI 启动会自动连接;首次配置需点击 Save and connect。 UI 管理的是当前进程,也可在页面上开关登录自启动。使用 UI 连接前先停止已有后台服务,避免同一设备重复连接。配置并启用自启动后可关闭浏览器标签页;远程操作使用运行 Agent 的账号权限。
本机 URL 包含本次 UI 的随机访问密钥,不要分享。API 还校验 Host 和 Origin,拒绝外部网页调用。设备权杖保存在设置文件中,并非加密保存;本机当前账号和管理员仍可读取。无 desktop-ui 特性的精简建置保持原有命令行行为,OpenWrt 与 Android 使用各自已有接口。
FreeBSD
官网提供 remote-freebsd-x64.tar.gz 与 remote-freebsd-arm64.tar.gz(在 Linux 上用 cargo zigbuild 交叉编译,链接 FreeBSD 14 libc)。解压后:
mkdir -p ~/.local/bin
cp remote-agent remote-cli ~/.local/bin/
sudo remote-agent install --system --device-id NAME --token TOKEN
当前源码支持 rc.d 系统安装、sysctl 指标、netstat -ibn 网口累计流量、FreeBSD 温度/风扇和 NetBSD envstat。具备 DISPLAY、python3 与 xdotool 时通告 X11 桌面控制;截图还需 Pillow。媒体与系统设置仍不可用。自更新可识别 BSD x64/arm64 归档,前提是发布清单存在对应的已验证包。这些改动需要新构建的安装包,仍需 BSD 原生运行验证。
NetBSD
官网提供 remote-netbsd-x64.tar.gz(amd64,在 Linux 上用 zig 链接 NetBSD 10 libc 交叉编译,动态依赖仅 libc / libpthread / libutil)。用法与 FreeBSD 相同:
mkdir -p ~/.local/bin
cp remote-agent remote-cli ~/.local/bin/
sudo remote-agent install --system --device-id NAME --token TOKEN
能力:连接 Exchange、exec / PTY / 文件传输;指标走 sysctl(kern.cp_time、hw.physmem64、vm.uvmexp2);桌面 GUI 控制为桩实现、不通告。设备平台上报为 netbsd。自更新与 rc.d 重启方式与 FreeBSD 相同(remote-netbsd-x64.tar.gz / remote-netbsd-arm64.tar.gz),前提是发布清单存在对应包。
OpenWrt 官方 25.12 软件包
官网为官方 OpenWrt 25.12.5 SDK 打包的 remote-agent(apk-tools,0.1.29-r1 包版本,内含 agent 0.1.29):
| target | 包架构 | 文件 |
|---|---|---|
| ipq40xx/generic | arm_cortex-a7_neon-vfpv4 | remote-agent-openwrt-ipq40xx-official.apk |
| x86/64 | x86_64 | remote-agent-openwrt-x86_64-official.apk |
| armsr/armv8 | aarch64_generic | remote-agent-openwrt-armsr-armv8-official.apk |
| mediatek/filogic | aarch64_cortex-a53 | remote-agent-openwrt-mediatek-filogic-official.apk |
| bcm27xx/bcm2711 | aarch64_cortex-a72 | remote-agent-openwrt-bcm27xx-bcm2711-official.apk |
| ramips/mt7621 | mipsel_24kc | remote-agent-openwrt-ramips-mt7621-official.apk |
| ath79/generic | mips_24kc | remote-agent-openwrt-ath79-official.apk |
LuCI 接口通用:luci-app-remote-openwrt-official.apk。用 apk --print-arch 核对架构后 apk add --allow-untrusted ./文件.apk。MIPS 包额外依赖 libgcc1(官方镜像缺省自带)。
Linux musl(Alpine)与 arm64
remote-linux-arm64.tar.gz:aarch64,glibc 2.28+(Debian 10+/Ubuntu 18.04+/RHEL 8+、树莓派 64 位系统等),支持remote-agent install注册 systemd 与自更新。remote-linux-musl-x64.tar.gz/remote-linux-musl-arm64.tar.gz:静态链接 musl,可在 Alpine 等无 glibc 的发行版运行。Alpine 默认无 systemd,请用remote-agent install --no-start写入配置后自写 OpenRC 服务(/etc/init.d/remote-agent)或前台运行。musl 构建支持update self与桌面自动更新,只安装 musl 包;一次性更新后依次尝试 systemd、OpenRC(rc-service remote-agent restart),再回退到进程重启。OpenWrt 主机上的 musl 包仍通过 opkg/apk 升级。
Linux
推荐用内置子命令(会拷贝当前可运行文件、写入私有设置并注册 systemd;不依赖仓库里的 install/ 文件):
cargo build --locked -p agent --no-default-features --profile release-small
# 用户级(默认,非 root)
./target/release-small/remote-agent install \
--config /path/to/config.json
# 或直接传凭证(无需先有文件;安装过程不访问网络)
./target/release-small/remote-agent install \
--device-id NAME --token TOKEN
# 系统级(root 或 --system):/usr/local/bin + /etc/remote-agent + multi-user.target
sudo ./target/release-small/remote-agent install --system \
--device-id NAME --token TOKEN
remote-agent status # 安装布局、服务、进程、连接(不打印令牌)
systemctl --user status remote-agent # 用户级
# systemctl status remote-agent # 系统级
remote-agent uninstall # 卸服务;加 --force 同时删二进制与配置
等价的手动步骤(仍可用):
install -Dm755 target/release-small/remote-agent ~/.local/bin/remote-agent
install -Dm600 /path/to/config.json ~/.config/remote-agent/config.json
install -Dm644 install/linux/remote-agent.service ~/.config/systemd/user/remote-agent.service
systemctl --user daemon-reload
systemctl --user enable --now remote-agent
用户服务缺省随登录会话运行。需要开机常驻时由管理员运行 loginctl enable-linger <user>,或使用上面的 --system 系统单元(RuntimeDirectory=remote-agent,WantedBy=multi-user.target)。远程命令使用服务账号权限。停止服务会回收服务内所有进程。install/linux/install.sh 是调用 remote-agent install --force 的薄封装。
Windows
安装 Rust MSVC 工具链和 Visual Studio C++ Build Tools 后:
cargo build --locked -p agent --release
.\target\release\remote-agent.exe --config C:\path\config.json
# 推荐:为当前登录用户安装常驻计划任务
.\target\release\remote-agent.exe install --config C:\path\config.json
# 已存在任务/文件时加 --force;仅注册不启动加 --no-start
Get-ScheduledTaskInfo -TaskName RemoteAgent
remote-agent status
remote-agent uninstall
remote-agent uninstall --force # 同时删除 %LOCALAPPDATA%\remote-agent 下的文件
install 拷贝二进位档和设置到 %LOCALAPPDATA%\remote-agent、限制设置文件 ACL,并注册登录时启动的调度工作 RemoteAgent。缺省若任务或目标文件已存在会报错;--force 会先停掉并注销旧任务再覆盖。该模式需要用户登录;不是 Windows SCM 系统服务。install/windows/install.ps1 是调用 remote-agent install 的薄封装(可加 -Force)。
如果先用 UI 生成设置,--config 可指向独立路径(例如 --ui --config .\device-config.json 生成的文件)。
Windows 命令使用 PowerShell 语法,例如 Get-Process,输出以 UTF-8 发送。每个运行会话和终端都有独立 Job Object;逾时、取消、断开或 agent 退出时清理其子进程树。
macOS
需要 Rust 工具链(rustup,含缺省的 aarch64-apple-darwin 或 x86_64-apple-darwin target)。指针经 sysctl、getloadavg(3) 与 mach host_statistics 采集,静态信息(型号、内核、系统版本)经 sysctl 与 sw_vers 采集;平台上报为 macos。
按需传感器(非心跳)在无 sudo、无额外 SMC 依赖下尽力采集:pmset -g batt 电池(笔记本)、pmset -g therm 的 CPU_Speed_Limit / Scheduler / Available CPUs 与热压力文案、ioreg 的 AppleSmartBattery Temperature 与风扇 RPM(若固件暴露)。台式机(如 Mac mini)常无电池;Apple Silicon 也可能无数值型 CPU_Speed_Limit。原始 CPU die / proximity 温度需私有 IOKit/SMC,当前不刮取。只要拿到热压力/限速或风扇读数,就不会把 thermal 标为 unavailable。激活 desktop-ui 后可用 remote-agent --ui 设置并开关登录自启动(LaunchAgent)。
cargo build --locked -p agent --release
# 推荐:安装为随登录会话运行的 LaunchAgent(无需 sudo)
./target/release/remote-agent install --config ~/.config/remote-agent/config.json
# 或:install/macos/install.sh ~/.config/remote-agent/config.json
remote-agent status
launchctl print gui/$(id -u)/us.ivjn.remote-agent | grep state
remote-agent install 把当前可运行文件拷贝到 ~/.local/bin/remote-agent,写入 ~/.config/remote-agent/config.json(权限 600),生成 ~/Library/LaunchAgents/us.ivjn.remote-agent.plist,并对二进位档运行 codesign -s - --force(macOS 会杀掉未签名的原地替换),再 launchctl bootstrap / kickstart。RunAtLoad + KeepAlive 让 agent 登录即启动、崩溃或断连后由 launchd 拉起。日志写入 ~/Library/Logs/remote-agent.log。install/macos/install.sh 是薄封装。未指定状态文件时使用 $HOME/.local/state/remote-agent/agent.state。远程命令以该登录用户的权限运行。
停止/卸载:
remote-agent uninstall
# 或手动:
# launchctl bootout gui/$(id -u)/us.ivjn.remote-agent
# rm ~/Library/LaunchAgents/us.ivjn.remote-agent.plist
需要开机(无人登录时)常驻时,改用 /Library/LaunchDaemons 下的 LaunchDaemon 并以 root 加载;该模式下远程命令以 root 权限运行,请据此评估风险。
Android 独立 APK
设备端项目在 android-agent/。mobile/ 仍是维运用户端,两者可以分别安装,不需要 Expo Go、Termux 或 root。
所有平台的 Agent 缺省地址为 wss://exchange.ivjn.us。Android 可点击 Connect with a six-digit code,由管理员 CLI 输入六位码授权后自动取得设备身份;原生 Agent(Windows/macOS 本地页面、OpenWrt LuCI「添加设备」、命令行 remote-agent pair / remote-agent join <邀请>)可显示六位码与二维码供 Console 扫描批准,或粘贴一次性邀请加入。详见 初始化与配对。
首次连接与主页
- 在需要被管理的 Android 设备上打开 Remote Agent。首次连接优先选择 使用六位验证码连接 或 扫描连接二维码,由管理员授权;账号、二维码和六位码的完整流程见 初始化与配对。
- 已有设备 ID 和设备令牌时,进入 设置 → 连接设置,填写服务端、设备 ID、设备令牌,再点 保存并连接。连接启用后,凭据输入区域隐藏;修改身份前先回主页断开连接。
- 主页显示连接状态、屏幕共享操作和管理者列表。已保存配置但尚未启用连接时,点 连接设备;已启用的设备重新打开应用时会恢复连接。
Android 12 及以上使用系统启动页,Android 8–11 使用同一品牌配色的启动背景。首屏就绪后进入主页,不等待网络连接完成,也不人为延长启动页;系统关闭动画时跳过过渡。设置页按需加载,打开主页不会自动授予辅助功能或屏幕共享权限。文档描述当前源码;公开下载包是否包含此体验,以实际安装版本为准。
后台运行、通知与停止
切到后台或关闭应用界面不会停止前台服务。网络断开时 Agent 会重连;启用连接后,设备重启并解锁、或应用包更新时会尝试恢复服务。强行停止应用后,需要手动重新打开;厂商省电策略也可能要求允许后台运行。
允许通知后,点 管理连接 返回应用管理连接。共享中的通知提供 停止共享,只结束屏幕共享。要关闭远程连接并禁用自动恢复,请在主页点 断开连接 并确认;保存的配置保留,下次可点 连接设备 重新连接。主页的 停止屏幕共享 同样只结束共享,不会断开远程访问。
设备令牌使用 Android Keystore AES-GCM 加密存储;应用不备份配置,也不通过导出的 Activity/Service 传递令牌。远程命令以 APK 的应用 UID 运行,受 Android 沙箱和 SELinux 限制,不能因此取得其它应用数据或 root 权限;系统设置、辅助功能与屏幕共享仍需各自授权。
远程访问启用期间,前台服务持有 CPU 和屏幕唤醒锁,退到后台或网络重连时也会保持亮屏,增加耗电。断开连接或服务销毁后释放唤醒锁,恢复系统原有的自动熄屏行为。手动按电源键锁屏仍由系统处理,不绕过锁屏认证。通知展示连接或共享状态,主页提供断开连接入口。
从源码构建
以下 Debug 与 Release 示例分别从仓库根目录开始执行。安装前用设备列表确认目标,并把 DEVICE_SERIAL 替换为目标设备序列号。打包、单元测试和完整 Lint 是不同检查;Lint 失败时查看报告并处理或记录原因,不要把跳过任务视为通过。
建置需要 JDK 17+、Android SDK Platform 36、NDK 28.2.13676358、Python 3 和 Rust。设置 ANDROID_HOME(或 ANDROID_SDK_ROOT);自定义 NDK 路径使用 ANDROID_NDK_HOME。全屏展示 .Aran 依赖 Metafile Android SDK:把 Metafile 仓库放在与 remote-control 同级的目录(Gradle 通过 ../../Metafile/android-sdk/aran 引用 :aran 模块)。
rustup target add aarch64-linux-android x86_64-linux-android
cd android-agent
./gradlew :app:assembleDebug :app:testDebugUnitTest
# Windows: .\gradlew.bat :app:assembleDebug :app:testDebugUnitTest
./gradlew :app:lintDebug
# Windows: .\gradlew.bat :app:lintDebug
adb devices -l
adb -s DEVICE_SERIAL install -r app/build/outputs/apk/debug/remote-agent-debug.apk
Gradle 自动调用 scripts/build-android-agent.py,把两个 ABI 的 Rust 可运行文件装入 APK 原生库目录。安装后直接从这个只读目录启动,符合 Android 对应用数据目录禁止运行文件的限制。debug APK 用于开发验证;正式分发须使用组织自己的签名设置建置 release,保管签名密钥以支持后续升级。
Release 建置读取 android-agent/keystore.properties(已被 Git 忽略),或环境变量 REMOTE_AGENT_SIGNING_PROPERTIES 指向的外部设置。字段见 android-agent/keystore.properties.example;密钥应保存在仓库外。缺少签名设置时 Release 建置会失败,不会回退到 Debug 签名。
cd android-agent
$env:REMOTE_AGENT_SIGNING_PROPERTIES = "$env:USERPROFILE/.remote-control/signing/remote-agent.properties"
.\gradlew.bat :app:assembleRelease :app:lintRelease :app:testReleaseUnitTest --no-daemon
# Output: app/build/outputs/apk/release/remote-agent-release.apk
请安全备份发布密钥及其口令,后续版本必须沿用同一密钥才能覆盖升级。Debug 与 Release 签名不同,已有 Debug 安装不能直接覆盖;卸载会清除本机设置和设备凭据,应先安排重新配对。scripts/build-site-downloads.py 使用签名的 Release APK 作为 Agent 下载包。Release 签名不保证解除 Play Protect 拦截。
Android 扫码连接
Remote Agent 首页的 Scan connection QR code 使用手机相机脱机识别连接二维码,无需 Google Play 服务。首次使用时允许相机权限;取消扫码不会修改设置。扫码成功后会显示服务器端和设备 ID,权杖保持隐藏。点击 Connect 后才加密保存设置并启动连接;已有连接运行时需先停止,才能导入新设置。扫码不会自动打开屏幕控制或无障碍功能。
先在 Exchange 登记一个独立设备并取得它的设备权杖,然后在电脑本地生成二维码(不要使用维运账号权杖或第三方在线二维码网站):
python -m pip install qrcode==8.2
python scripts/connection-qr.py --server wss://exchange.example.com --device-id android-phone --output "$env:TEMP\android-phone-connection.svg"
# 按提示输入设备令牌,输入不会显示。也可用 --token-stdin 从标准输入读取。
在电脑上打开生成的 SVG,让 Remote Agent 扫描。二维码文件包含设备权杖,应保密并在使用后删除,不要提交到 Git。工具拒绝覆盖已有文件。
二维码内容为 UTF-8 JSON,总长度最多 2048 字节:
{"type":"remote-agent","version":1,"server":"wss://exchange.example.com","device_id":"android-phone","token":"DEVICE_TOKEN"}
扫码仅接受 wss:// 地址,可带 /ws/device 路径,不接受 URL 用户信息、查找参数或片段。server、device_id、token 必须为非空字符串,分别最多 512、128、512 个字符,不能包含控制字符或首尾空白。手工设置仍可用于本地 ws:// 测试环境。
Android 无障碍功能、截屏与文本输入
使用 remote-cli 执行以下命令。设备通过 APK 内部桥接上报 android_control 能力;权限是否已授予以 status 为准。
- 先连接设备,再进入 设置 → 屏幕与交互,开启 允许远程屏幕控制(默认关闭)。
- 点 无障碍设置,在 Android 系统设置中启用 Remote Agent。它允许读取可访问的界面树、点击、滑动、系统导航和替换焦点输入框的文本。
- Android 11+ 可通过已启用的辅助功能截图。Android 8–10,或仅需要屏幕捕获时,回主页点 共享屏幕,或在设置中点 标准屏幕共享,确认系统屏幕共享对话框。共享会话结束或进程重启后需要重新授权。
remote-cli ui my-device status
remote-cli ui my-device tree
remote-cli ui my-device xml > screen.xml
remote-cli ui my-device click --view-id com.example:id/search
remote-cli ui my-device click --text "Search" --description "Open search"
remote-cli ui my-device screenshot --output screen.jpg
remote-cli ui my-device tap 300 500
remote-cli ui my-device swipe 500 1500 500 500 --duration-ms 400
# 多段滑动(折线,2-64 个点)
remote-cli ui my-device swipe-path 300,1500 300,800 900,800 --duration-ms 800
remote-cli ui my-device key back
remote-cli ui my-device text "Hello"
remote-cli ui my-device text --stdin < message.txt
remote-cli ui my-device type --stdin < message.txt
text 使用 Android ACTION_SET_TEXT 替换当前焦点输入框的全部文本,支持中文、表情和多行,最多 8192 个 UTF-8 字节,不允许 NUL。先点击可编辑输入框;未聚焦、不可编辑或不支持此动作的控制项会返回错误。--stdin 读取 UTF-8 并保留换行,也避免把文本放入命令行历史。key 支持 back、home、recents、notifications、quick-settings。
xml 返回嵌套的 <hierarchy><node ...> 接口树,包含 resource-id、text、content-desc、bounds 和控制项状态;最多 200 个节点、50 层,属性最多 256 个字符,响应和根元素都会标明是否截断。click 使用这些属性的完整值精确匹配(不是 XPath):--view-id 对应 resource-id,--description 对应 content-desc;多项条件按 AND 组合。只匹配可见、激活的控制项,零个或多个匹配都会报错;遍历超限也不会运行点击。文本标签会寻找可点击的父控制项,无法点击时可用 tap X Y。接口变化后应重新获取 XML,长属性可改用资源 ID 定位。
输入法输入的入口在 设置 → 远程输入:先点 启用远程输入 在 Android 中启用 Remote Input,再点 选择输入法 选中它。聚焦目标应用输入框后,type 通过 InputConnection.commitText 在光标/选区插入文本,支持中文、Emoji 和多行,使用与 text 相同的 8192 UTF-8 字节限制。它不会自动点击发送或提交表单,也不能修改 Agent 自身设置。键盘上的 Switch keyboard 可恢复日常输入法。status 返回 ime_enabled、ime_selected 和 ime_connected。读取和点击接口、输入文本前必须亮屏并在设备上解锁。
Remote Console 的 设备屏幕 页面提供座标点击、XML 获取与属性点击、Remote Input 文本输入。不可用的权限或屏幕状态会禁用按钮;输入成功会清空正文,页面进入背景时清除截屏、XML 和待输入内容。
系统能力
系统设置按设备回报的提供者工作,命名空间是字符串,不是写死的枚举。Android 仍使用能力 android_settings,提供者是 system、secure、global。Linux(不含 OpenWrt)和 macOS 使用能力 system_settings:Linux 当前用户的 gsettings,macOS 当前用户的 /usr/bin/defaults。浏览器扩展使用能力 browser_settings,经加密会话读写 privacy.network、privacy.services、privacy.websites。Windows 和 OpenWrt 不提供这组设置。添加一种键值设置时,在 Agent 或扩展里登记提供者即可,操作端跟着清单渲染。
Android 的三个命名空间与屏幕开关、显示器和锁屏状态无关,通过 ContentResolver 操作而非 shell 拼接。普通 APK 的 system 写入需在 Agent 点击 允许修改系统设置 并授权;secure/global 写入需要额外 WRITE_SECURE_SETTINGS 系统权限。状态返回当前权限和 providers,Android 和厂商策略仍可拒绝单项读写。
settings list --prefix 在 Android 上过滤设置名;在 Linux / macOS 上,不带 prefix 只返回 schema 或 domain 名称,带 prefix 返回这一个 schema 或 domain 的顶层字符串、整数和布尔值。settings clear 目前只有浏览器隐私项会接受,其他提供者会返回明确错误。审计记录命名空间、设置名和字符数,不记录值。
remote-cli ui realmev5 settings
remote-cli ui realmev5 settings list --namespace system --limit 100
remote-cli ui realmev5 settings get screen_off_timeout
remote-cli ui realmev5 settings set screen_off_timeout 600000
remote-cli ui realmev5 screen-timeout 600
remote-cli ui realmev5 settings get device_name --namespace global
remote-cli ui realmev5 settings set vendor.custom_setting --stdin
settings set 的值为字符串,--stdin 保留换行;设置值不写入审计。值最多 8192 UTF-8 字节,不允许 NUL;设置名最多 256 个 ASCII 字符(字母、数字、下划线、点、冒号、短横线)。列表最多 500 项、512 KiB,超限返回 truncated;单项值最多 64 KiB。不存在的设置返回 value: null。接口不会静默强制修改受限设置,每次写入后重新读取确认,失败返回明确错误。修改会持久生效,断开 Agent 不恢复旧值。screen-timeout 快捷命令使用秒(15–86400),底层 screen_off_timeout 使用毫秒;自动息屏与息屏后的锁定延迟不同。
媒体播放(media)
朗读、音频、图片(含全屏文本)、视频和网页统一走 media 能力,在 Android 与 Windows/Linux/macOS Agent 上通告;OpenWrt 不通告。协议只有三个动作:media_play(带一个 media 对象)、media_stop、media_status,三者都返回同一个 media 状态。
media.type |
字段 | 信道 |
|---|---|---|
speech |
text,可选 language(BCP-47,如 zh-CN) |
声音 |
audio |
url 或 path 二选一,可选 loop |
声音 |
image |
url、path、text 三选一,可选 p2p(text 不可带) |
画面 |
video |
url 或 path 二选一,可选 p2p |
画面 |
web |
url(仅 https) |
画面 |
设备上有两个互相独立的信道:声音(朗读、音频)和画面(图片、文本、视频、网页)。新的内容只替换同一信道里正在播放的那个,所以背景音乐可以和图片轮播同时进行。media_stop 的 channel 可取 sound、screen 或 all(缺省)。
remote-cli ui realmev5 media speak "Remote 连接正常" --language zh-CN
remote-cli ui realmev5 media speak --stdin --language zh-CN
remote-cli ui realmev5 media audio --path /sdcard/Music/doorbell.mp3
remote-cli ui realmev5 media audio --url https://cdn.example.com/notice.mp3 --loop
remote-cli ui realmev5 media image --text "值班中"
remote-cli ui realmev5 media image --url https://example.com/poster.jpg
remote-cli ui realmev5 media video --path /sdcard/loop.mp4
remote-cli ui realmev5 media video --url https://files.example.org/library/clip.Aran --p2p-group library --p2p-discovery https://p2p.ivjn.us --p2p-token secret --p2p-peer 192.0.2.10:7300
remote-cli ui realmev5 media web https://status.example.com/board
remote-cli ui realmev5 media status
remote-cli ui realmev5 media stop sound # 或 screen;不写则两个通道都停
remote-cli ui laptop media audio --path 'C:\Users\me\Music\alert.wav'
remote-cli ui laptop media image --text "Kiosk"
media status 返回:
{"kind":"media",
"sound":{"type":"audio","id":"…","state":"playing","language":null,"loop":true},
"screen":{"type":"web","keep_awake":true},
"speech_available":true,"media_volume":7,"media_volume_max":15}
sound 是声音信道最近一项(结束后保留,便于查看结果),状态为 queued / speaking / loading / playing / completed / stopped / error;从未播放过时为 null。screen 只在画面正在展示时出现,否则为 null。speech_available=false 表示设备上没有可用的语音引擎。媒体音量只在 Android 上有值,桌面端为 0。
校验与审计:文本(朗读或图片文本)最多 4000 UTF-8 字节、不允许 NUL;URL 必须是 https,最多 2048 字节,不能含空白或控制字符;路径最多 4096 字节,不能含控制字符。审计只记录类型、来源类别(url / path / text)、字符数、是否循环和是否使用 P2P,从不记录 URL、路径或正文(URL 可能带签名权杖)。readonly 账号不能调用任何媒体操作。
声音:朗读与音频
- 朗读:Android 使用系统 TTS 引擎;Windows 使用
System.Speech;macOS 使用say;Linux 使用spd-say/espeak-ng/espeak(均未安装时speech_available=false,朗读返回明确错误)。桌面端会尽量把语言标签映射到本地语音。Android 在引擎开始出声后才返回;系统引擎初始化或语言数据不可用时返回错误;停止 Agent 会释放 TTS。 - 音频:来源为设备上的绝对路径或 https URL。设备上没有的文件先用
remote-cli files upload传上去再按路径播放。loop循环播放直到停止。- Android 用
MediaPlayer走媒体音量,URL 直接流式播放,开始出声后才返回,10 秒内未能开始则返回错误。 - 桌面端先把 URL 下载到当前用户的临时状态目录(上限 50 MB,重定向只允许 https),播完、停止或出错都会删除;接口在播放进程启动后即返回,下载或解码失败通过
media status的sound.state=error体现。Windows 用MediaPlayer(支持 mp3、wav、m4a、aac、wma、flac);macOS 用afplay;Linux 依次查找mpv、ffplay、paplay、pw-play、aplay,均未安装时返回错误(paplay/aplay不支持 mp3)。 - 路径必须是存在且可读的普通文件;Windows 拒绝
\\server\share这类网络路径,避免向远程 SMB 服务器发送凭据。
- Android 用
- 设备音量为零时可能听不到声音。
画面:图片、文本、视频与网页
- image:本机
path、httpsURL,或text(深色背景上的大号 UTF-8 文本,按静态图片处理)。Android 上扩展名为.Aran的来源会用 Metafile Android SDK 解密后再显示 - video:本机
path或httpsURL,循环播放。Android 上.Aran不能当普通 HTTPS 媒体流:未缓存时按块解密边下边播,并把明文写入本地缓存供下次直接播放。HTTP 块地址为该 origin 根路径下的/meta/<SHA1>;本机文件则读取清单旁边的meta/目录。缺省使用清单内嵌密钥或 SDK 缺省密钥。可选 P2P:块源顺序为本机meta/、UDP P2P(LOOKUP_ONLY,不注册进 metastore)、HTTP 回退。发现服务与静态 peer 由media.p2p提供,例如--p2p-group library --p2p-discovery https://p2p.ivjn.us;P2P 启动失败时仍走 HTTP/本机,不中断播放 - web:全屏打开一个
https页面。Android 用全屏 Activity 内的 WebView(打开 JavaScript 与 DOM storage,页面内导航只跟随 https;渲染进程崩溃时关闭展示而不影响 Agent)。桌面端以 kiosk 模式启动独立浏览器进程,使用临时设置目录,停止时连同浏览器一起结束:Windows 依次查找 Edge、Chrome、Firefox;macOS 查找 Chrome、Edge、Chromium、Firefox(Safari 没有 kiosk 模式);Linux 查找 Chromium、Chrome、Edge、Firefox。找不到浏览器时返回明确错误
Android 对 https://cdn.ivjn.cn 与 https://cdn.ivjn.us 的 .Aran 清单和数据块自动使用另一域名作为备用源,保留清单路径,数据块仍读取各源的 /meta/<SHA1>。其他域名不会自动映射。暂时性错误(含 HTTP 554)、逾时及数据块校验失败时先尝试备用源,再按 250/500 ms 退避,最多 3 轮;成功后优先使用该源。每次连接逾时 5 秒、读取逾时 30 秒;403/404 等永久错误不在同次读取中重复请求同一源。仍校验块大小和 SHA-1,不跟随重定向、不放宽 HTTPS 校验。此行为需要 Metafile ab2b92f 或更新版本的 Android SDK(包含 MirroredHttpSource)。
画面信道一次只展示一项;新的画面替换上一项。展示期间保持亮屏(Android:FLAG_KEEP_SCREEN_ON + turnScreenOn / showWhenLocked;Windows:SetThreadExecutionState;macOS:caffeinate -d;Linux:systemd-inhibit 或 xdg-screensaver inhibit)。静态图片与文本缓慢像素漂移以防灼屏;视频仅循环;网页不漂移。无静态高亮边框。Android 通过独立全屏 Activity(无需无障碍功能)。Linux 画面需要未锁屏的 X11 会话,视频需本机安装 mpv 或 ffplay;macOS 视频优先 mpv,否则 open。
控制:进度与网页
media_control 按信道控制正在播放的内容,返回同样的 media 状态。channel 必须是 sound 或 screen;op 取下表之一:
op.type |
参数 | 适用 |
|---|---|---|
pause / resume |
— | 音频(sound)、视频(screen) |
seek |
position_ms(绝对位置,最多 7 天) |
音频、视频 |
navigate |
url(https) |
网页 |
reload / back / forward |
— | 网页 |
scroll |
dx、dy(像素,非零,各不超过 100000) |
网页 |
eval |
script(最多 16 KiB) |
网页,返回 script_result(结果的 JSON 编码,最多 64 KiB) |
remote-cli ui realmev5 media pause sound
remote-cli ui realmev5 media seek 1:30 screen # 也接受 90、1:02:03.5
remote-cli ui realmev5 media resume screen
remote-cli ui realmev5 media web go https://status.example.com/next
remote-cli ui realmev5 media web back
remote-cli ui realmev5 media web scroll 0 800
remote-cli ui realmev5 media web eval "document.title"
状态里添加:sound / screen 的 position_ms、duration_ms、paused(音频和视频,播放器能报告时),网页时 screen 的 url 与 title。朗读、图片和文本不能控制;信道里放的内容与操作不匹配时返回明确错误。审计只记录信道、操作类型和脚本字符数,不记录网址或脚本内容。
- Android:音频与视频直接控制
MediaPlayer/VideoView,网页控制全屏 Activity 内的 WebView。 - Windows:音频由自带的
MediaPlayerworker 运行;视频在 mpv 下经 IPC 管道控制,WPF 回退播放器也可控制,ffplay 不支持。 - Linux / macOS:音视频只在播放器是 mpv 时可控(JSON IPC),否则返回「Install mpv to pause or seek desktop media」;macOS 装有 mpv 时音频也改用 mpv 播放。
- 桌面网页:Chrome / Edge / Chromium 的 kiosk 以
--remote-debugging-port=0启动(只绑 127.0.0.1、随机端口、临时设置目录),经 DevTools 协议运行;Firefox 只支持navigate(以新地址重开 kiosk)。
Remote Console 手机端在设备详情的 系统能力 页面提供「系统设置」与「媒体」页签;媒体页可读取两个信道的状态、分别停止声音或画面,播放朗读、音频 URL、文本、图片、视频和网页,并控制音视频进度与网页(跳转、前进后退、刷新、滚动、运行 JS)。页面进入背景会清空设置结果、待发送设置值、朗读正文和展示输入。网页控制台提供同一份设置表单,浏览器设置复用已经创建的加密会话;网页端不另做媒体播放页。
Android 文本搜索与 XML
remote-cli ui realmev5 xml
remote-cli ui realmev5 find-text "设置"
remote-cli ui realmev5 find-text "Settings" --exact --ignore-case
文本搜索同时匹配可见节点的文本和描述,缺省包含匹配,可选精确匹配、忽略大小写。返回每个匹配的 bounds 和 center(原始屏幕像素,可用 tap X Y 点击)、资源 ID、文本及激活状态。中心点取与屏幕交集,避免返回屏幕之外的点击座标;结果最多遍历 200 节点并报告截断。密码节点和 Agent 自身设置子树不参与搜索。自绘文本可能无法被 Android 无障碍功能读取,本接口不运行 OCR。接口变化后应重新搜索。
截屏为 JPEG,最长边最多 1600 像素,CLI 拒绝覆盖已有文件。CLI 同时显示图像与原始屏幕尺寸;点击、滑动和树节点 bounds 使用原始屏幕像素。根据缩略图定位时,使用 屏幕坐标 = 图像坐标 × screen_width / width(纵向使用高度比例)。旋转后重新读取尺寸。接口树最多 200 个节点,每个字符串最多 256 个 UTF-16 单元,truncated 表示节点数量受限。应用自绘接口可能没有可访问节点。
密码节点的文本和描述、Remote Agent 自身的设置子树不会返回;远程文本输入不能修改自身设置。受 Android FLAG_SECURE 保护的窗口在截屏中被遮黑或返回系统错误;Android 15 的屏幕共享还可能自动遮黑含密码字段等敏感内容的应用。屏幕控制复用现有维运身分验证与授权,readonly 账号不能发起这些操作。审计只记录动作、座标和输入字符数,不保存输入正文、接口树或截屏。
Stop screen sharing 结束 MediaProjection 共享会话;若 Android 11+ 无障碍功能仍激活,无障碍功能截屏仍可用。关闭 Allow remote screen controls 会阻止后续所有屏幕操作并结束共享;Stop remote access 同时断开 agent。已经运行的点击或输入无法撤回。无障碍功能权限由 Android 设置单独管理;开关关闭或服务停止时仍保留系统授权,但不接受远程屏幕操作。
本地桥接使用随机名称的 Unix socket,并校验调用者与 APK 的 UID 相同。这保护其它普通应用的访问边界;已授权的远程 shell 与 APK 共用应用 UID,屏幕开关不应被当作针对该 shell 或设备管理员的权限隔离。
系统信息与指针
DeviceInfo.platform 为 linux、freebsd、netbsd、windows、android、macos 或 openwrt,os_version 为通用系统版本,capabilities 描述 exec、pty、ubus、android_control。OpenWrt 上报 openwrt_version。
Linux/OpenWrt 从 /proc 采集指针;Android 遇到 SELinux 限制时保留其它可读指针,运行时长可通过 CLOCK_BOOTTIME 取得。Windows 通过原生系统 API 采集内存、CPU、运行时长和网络卡计数。无法采集的指针在 unavailable 列表中标记,CLI 和移动端显示缺项。Windows 没有 Unix load average,因此 load 总被标记为不可用。
验证
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo test -p agent --features desktop-ui
cargo clippy -p agent --features desktop-ui --all-targets -- -D warnings
cd mobile && npm run typecheck && npm run lint && npm test
scripts/agent-smoke.py 是本地测试 relay,使用 Python websockets,验证真实 agent 的注册、指针、运行、逾时、取消、PTY、resize 和不支持的 ubus。它不替代 Exchange 身分验证与授权与数据库测试。按脚本说明使用固定测试凭证,勿连接正式设备或服务。Windows 和 Linux 都应运行各自的原生测试;Android 还应安装 APK 后在实际运行环境验证前景、背景、停止及重连。
scripts/agent-ui-smoke.py --agent <binary> --platform linux|windows 另行验证 UI 的首次设置、身分验证与授权与来源限制、私有文件保存、保留权杖、实际注册和运行、停止回收、断线重连及重新打开。要求 Python 3.11+ 和 websockets;必须先用 desktop-ui 建置待测 agent。浏览器验证还应覆盖保存按钮、输入错误、权杖显隐、刷新恢复与窄屏布局。
scripts/android-control-smoke.py 使用真实 Exchange、CLI 和独立 Android 测试应用验证屏幕操作。仅在专用、可丢弃的仿真器运行:脚本会清除 Remote Agent 应用数据并修改无障碍功能设置。先建置并安装 :app:assembleDebug 与 :control-fixture:assembleDebug 两个 APK,准备独立 Exchange/数据库,登记测试设备并保存设备权杖及 CLI 设置;需要 Python 3.11+、Pillow 和 adb。
adb -s emulator-5580 install -r android-agent/app/build/outputs/apk/debug/remote-agent-debug.apk
adb -s emulator-5580 install -r -t android-agent/control-fixture/build/outputs/apk/debug/control-fixture-debug.apk
python scripts/android-control-smoke.py --serial emulator-5580 \
--cli target/debug/remote-cli --cli-config /private/qa-cli.toml \
--device-token /private/qa-device-token.txt \
--device-id android-control-qa --device-server ws://10.0.2.2:18820/ws/device
Windows 将 CLI 路径改为 target/debug/remote-cli.exe。测试产物缺省位于 target/android-control-qa/screenshots/。fixture 仅用于测试,不打入设备端 APK。
Android 日志、显示状态与唤醒
Remote Console 在设备详细信息中添加 Device logs(设备日志) 和 Device screen(设备屏幕)。屏幕页面会显示是否有显示器、电源状态、锁定状态、尺寸与权限,并提供随需截取的 JPEG 预览与 Wake screen(唤醒屏幕)。没有显示器或 Android 控制接口的设备会停用屏幕操作;缺少截取权限会另行显示。激活本机屏幕控制后,可以唤醒已熄灭的屏幕。唤醒不会解除锁定画面。
Android Agent 本机提供 Screen status and device logs(屏幕状态与设备日志)。激活远程访问期间,连接字段、QR 注册及连接代码按钮会隐藏。中断连接需要确认,并会保留已保存的凭证。无障碍功能授权与服务是否已绑定会分别回报;若已授权但仍在等待绑定,请在系统无障碍设置中仅切换 Remote Agent 的开关。
remote-cli ui realmev5 status
remote-cli ui realmev5 logs --limit 100
remote-cli ui realmev5 wake
remote-cli ui realmev5 screenshot --output screen.jpg
既有的 open_android WebSocket 接口接受 {action:"logs",limit:100}(1–300 笔)与 {action:"wake"}。状态添加选用字段 display_present、screen_on、locked、screen_width、screen_height 和 accessibility_authorized;accessibility_enabled 仍表示服务已连接。Android 原生屏幕 API 仍需要 APK,非 Android agent 不会声明此接口。
日志现在取自 logcat -b all,不依 PID、套件或标签筛选,涵盖 Android logd 可提供的系统、应用程序、事件与当机缓冲区。每次快照在 UI 最多回传 100 笔,在 CLI 可回传 1–300 笔,保留时间戳记、层级、缓冲区名称、PID/TID、标签与原始消息。这是设备目前的 logcat 历史,并非任意日志文件或 Android 已轮替移除日志的封存。
通过已授权的 ADB 连接,授予设备 APK 系统日志权限:
adb shell pm grant com.remote.agent android.permission.READ_LOGS
若 Android 版本要求每次使用时授权日志访问,请打开 Agent 诊断页面并接受系统确认,再重试远程请求。Android/OEM 政策可能拒绝背景访问,或让核准失效。拒绝后,Android 可能仅回传调用者 UID 的日志而不提示,因此收集器会先检查最新 301 笔 logcat 的 UID 中继数据,再选取要求的末尾笔数。若没有出现其他 UID(包括日志为空的情况),会回报无法确认整台设备的访问权,而不会显示只有本应用程序的快照。缺少权限或 logcat 失败也会回传明确错误;收集器不会退回使用 Agent 事件环形缓冲区。收集工作在 UI 线程之外运行,取消时停止,并设有六秒期限与输出上限;过大的快照会回报截断或错误。日志沿用既有的已验证操作员工作阶段、租户路由与角色限制。设备日志文本可能包含其他应用程序写入的内容。网页与行动端日志查看在离开页面或切换至背景时会清空,不会将快照存入磁盘。Remote Console 也会在离开或切换至背景时清除屏幕截取画面。
透明全屏幕分享: 在 Android Agent 选择 Share full screen with transparent overlay(通过透明复盖层分享全屏幕)。授予 Android 的显示于其他应用程序上层权限,并同意屏幕分享。Agent 随后会切换至背景,创建透明度为零、设置 FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE 的全屏幕 TYPE_APPLICATION_OVERLAY:所有触摸都会传给下方的应用程序。MediaProjection 截取完整显示画面(Android 14+ 会明确要求缺省显示器)。可从 Agent 页面或通知停止分享;停止分享不会中断 Agent 连接。分享结束或服务停止时会移除覆盖层。
屏幕预览与已连接的 Agent 状态页面允许截取画面。连接设置、登录票证与权杖 QR 页面仍受保护。Android 仍可能针对其他应用程序的安全窗口回传黑色影像;透明复盖层无法绕过该应用程序的保护。
截取前,Android Agent 会同时检查显示器状态与 PowerManager 的交互状态。它只会唤醒已熄灭的显示器,确认显示器已进入可交互状态,并在锁定画面仍锁定时拒绝截取。MediaProjection 读取器会持续取出影格并保留一张目前的合成影像,不会为每次截取画面重新创建 surface。遇到全黑影格时会等待可用影格,逾时后回传可据以处理的错误,而不会将全黑 JPEG 当作成功结果。
Agent 模式对照
启动方式与设备平台是两个维度。原生 headless、本机 UI 和系统服务共用连接核心;desktop-ui 只增加本机配置界面,不增加远程权限。
| 模式/平台 | 提供的能力 | 条件与边界 |
|---|---|---|
| headless / 本机 UI | 同一原生协议、命令、终端、文件、指标、通知、可选 LAN | UI 仅 Linux/Windows/macOS;Windows 有托盘;桌面操作需要可用的用户图形会话 |
| 系统服务 / 登录自启动 | 同一原生 Agent | 继承服务账号及环境;启动参数不会自动同步到另一个服务 |
| OpenWrt | 原生核心、ubus | ubus 存在时通告;procd 监管;不通告桌面控制 |
| Linux / Windows / macOS | 原生核心、桌面控制、系统日志、媒体 | 依赖系统权限/工具;系统设置仅 Linux/macOS 通告 |
| FreeBSD / NetBSD | 原生核心、终端、文件、网络、指标、可选 LAN | 电池不可用;X11 依赖;无媒体/系统设置 |
| Android APK | Rust 核心 + Java 控制桥,屏幕、设置、媒体、OTA | 应用 UID 和系统授权范围;单独运行 Rust 二进制没有 APK 控制桥 |
| Chromium 扩展 | URL、Cookie、网页、浏览器设置、CDP、指标 | 无系统命令/PTY/任意宿主文件/LAN 监听;完整 CDP 需要本机许可 |
UI 和 headless 共用运行配置解析:LAN、watchdog、TLS 及通知遵循命令行/环境变量优先于 JSON;自动更新保持命令行 > JSON > REMOTE_AUTO_UPDATE > 默认开启。REMOTE_WATCHDOG=1 和 0、true/false、yes/no、on/off 均可解析。UI 仍拒绝连接凭证及状态路径的启动覆盖;运行覆盖不写回 JSON,自启动进程需另行配置。LAN 状态显示当前生效值,页面保存不会取消启动覆盖。
UI 与 headless 使用 JSON 的 state_file,否则使用同一系统默认持久路径;多个设备实例应各自指定路径。连接开始时刷新 connecting,停止时显示 stopped。停止连接会取消该连接的自动更新 future,并释放 watchdog 状态;后台监控在下一次检查时退出,已经完成的操作不能撤销。 每次重新连接还会重新加载设备身份和信任策略;加载新身份失败时清空旧身份,避免继续使用旧设备授权。
所有设备操作都要求密封会话与工作区根授权。浏览器完整 CDP 的逐次确认/无人值守许可不受类型化 Cookie 范围隔离,也不能绕过 Chrome 策略。扩展关闭或断线后要重新握手。桌面自更新包映射 Windows x64、Linux glibc x64/arm64、macOS arm64;其它原生平台使用安装包/包管理器,Android 使用 APK OTA,浏览器使用扩展包。能力通告不代表硬件、权限或发布包一定可用。
自动屏保
本机 Agent 或浏览器扩展的屏保设置支持无操作(默认 300 秒)、控制开始/结束和立即进入,内容可选文字、HTTPS 图片/视频或隔离的静态 HTML。视频静音循环,文字和图片可大范围移动。桌面和 Android 可设置退出密码;浏览器全屏窗口不设置密码,按键、移动鼠标或关闭窗口即可退出。仅覆盖活动显示器,不代替系统锁屏。Linux 需要 X11 与 xprintidle;无桌面 OpenWrt、Wayland 和 BSD 当前不支持屏幕展示。
纯色模式仅显示所选底色,不显示文字或媒体。底色使用 #RRGGBB,亮度为 0–100%(0% 全黑)。Android 同时降低屏保窗口背光,触摸或按键时恢复系统亮度;其他平台仅调暗画面,LCD 节能取决于背光。退出密码提示保持可读。
从 0.1.34 起,开启移动时仅文字和图片在整屏黑底区域内大范围移动;大内容按需缩小至可用区域宽、高的 44% 以内,使相对角落的位置不重叠,避免同一区域持续显示内容。HTML、视频和纯色保持静止,系统“减少动态效果”设置会暂停移动。所有模式从页面首帧隐藏鼠标,退出密码框显示时恢复,取消后重新隐藏;Windows 每 100 毫秒刷新屏保窗口内的光标状态,不修改系统光标。首批提供 Windows 与 Browser Agent 0.1.34 包,其他平台以下载清单中的实际版本为准。