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。首次連線優先選擇 使用六位驗證碼連線 或 掃描連線二維碼,由管理員授權;帳號、QR 碼與六位碼的完整流程見 初始化與配對。
- 已有裝置 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 套件,其他平台請以下載清單的實際版本為準。