# Các thiết bị Linux, FreeBSD, NetBSD, Windows, macOS và Android

> HTML: https://remote.ivjn.us/docs/vi/platforms/

Các thiết bị Linux (glibc/musl), FreeBSD, NetBSD, Windows, macOS và Android tái sử dụng `remote-agent`, Exchange, `remote-cli` và các máy khách vận hành và bảo trì di động. Phía thiết bị và CLI sử dụng tên chung; các biến môi trường đều có `REMOTE_*` thống nhất.

## Cài đặt một lệnh (Windows/Linux/macOS/FreeBSD/NetBSD)

Tập lệnh cài đặt chính thức tải xuống và xác minh gói dành cho hệ thống của bạn, đặt các tệp nhị phân trong `~/.local/bin`, rồi gọi `remote-agent install` để đăng ký Agent tự khởi động khi người dùng đăng nhập:

Chọn lệnh cho vai trò của bạn và sao chép từng lệnh riêng biệt.

**Cài đặt Agent trên thiết bị được quản lý**

```sh
curl -fsSL https://remote.ivjn.us/install.sh | bash -s -- agent
```

**Cài đặt CLI trên máy vận hành**

```sh
curl -fsSL https://remote.ivjn.us/install.sh | bash -s -- cli
```

Windows chạy trong Git Bash. Chuyển thông tin xác thực Agent sau đường ống: `bash -s -- agent --device-id NAME --token TOKEN` (hoặc đã có `config.json` / `REMOTE_CONFIG`). Các biến môi trường được viết trước `curl` không thể truy cập bash. Hệ thống cấp hệ thống Linux có thể thêm `--system`. Tập lệnh sẽ tự động phân biệt giữa glibc và musl (Alpine, v.v.) và Linux arm64 (aarch64) cũng được hỗ trợ; `REMOTE_LIBC=musl` có thể được sử dụng để buộc lựa chọn gói musl. FreeBSD / NetBSD: các lượt cài đặt không có đặc quyền vẫn còn trong `~/.local/bin`; chạy `sudo remote-agent install --system --config CONFIG` để đăng ký RC.d. Điều khiển máy tính để bàn yêu cầu phiên người dùng X11 với `python3` và `xdotool`. Sử dụng các gói dành riêng cho Android và OpenWrt.


## Lệnh và dịch vụ

Các lệnh CLI và thiết bị là `remote-agent` và `remote-cli`. `remote-cli` ghi vào `remote-cli/config.toml` theo mặc định; `REMOTE_CLI_CONFIG` rõ ràng được ưu tiên.

Gói OpenWrt, tệp UCI và dịch vụ init được đặt tên là `remote-agent`. Tệp trạng thái là `/var/run/remote-agent.state` và tệp cấu hình là `/etc/config/remote-agent`. Plugin LuCI `luci-app-remote` phụ thuộc vào gói này. `REMOTE_AGENT_SRC` đặt đường dẫn nguồn xây dựng.

| Thiết bị | Thực thi lệnh | Thiết bị đầu cuối tương tác | Chế độ thường trú | ubus |
|---|---|---|---|---|
| Linux | `/bin/sh -c` | Unix PTY | systemd hoặc quá trình tiền cảnh | Chỉ OpenWrt |
| Linux musl (Alpine 3.x, v.v.), x64/arm64 | `/bin/sh -c` | Unix PTY | Quá trình tiền cảnh hoặc dịch vụ OpenRC tự viết (không có systemd) | Không được hỗ trợ |
| FreeBSD 14+, amd64/arm64 | `/bin/sh -c` | Unix PTY | rc.d (gốc/hệ thống); tiền cảnh (người dùng) | Không hỗ trợ |
| NetBSD 10+, amd64 | `/bin/sh -c` | Unix PTY | rc.d (gốc/hệ thống); tiền cảnh (người dùng) | Không hỗ trợ |
| Windows 10 1809+/11, Máy chủ 2019+ | Windows PowerShell 5.1 | ConPTY + PowerShell | Nhiệm vụ theo lịch trình khi đăng nhập hoặc quá trình tiền cảnh | Không được hỗ trợ |
| macOS 11+, Apple Silicon/Intel | `/bin/sh -c` | Unix PTY | launchd LaunchAgent hoặc tiến trình tiền cảnh | Không được hỗ trợ |
| Android 8.0+, arm64/x86_64 | `/system/bin/sh -c` | Unix PTY | Dịch vụ tiền cảnh APK độc lập | Không được hỗ trợ |

## Cấu hình chung

Trước tiên, hãy đăng ký thiết bị thông qua `remote-cli device add <id>` và nhận mã thông báo thiết bị. Linux/Windows tạo `config.json` mà chỉ tài khoản đang chạy hiện tại mới có thể đọc được:

```json
{
  "server": "wss://mgmt.example.com",
  "device_id": "my-device",
  "token": "REPLACE_WITH_DEVICE_TOKEN"
}
```

```sh
remote-agent --config /path/to/config.json
```

Ưu tiên là các biến môi trường dòng lệnh/`REMOTE_SERVER`, `REMOTE_DEVICE_ID`, `REMOTE_TOKEN`, sau đó là JSON và cuối cùng là dự phòng UCI chỉ ở OpenWrt. JSON cũng có thể đặt `state_file`; dòng lệnh `--state-file` / `REMOTE_STATE_FILE` ghi đè nó. Khi không có tệp trạng thái nào được chỉ định, OpenWrt vẫn sử dụng `/var/run/remote-agent.state`; các nền tảng khác sử dụng `remote-agent/agent.state` trong `LOCALAPPDATA`, `XDG_STATE_HOME` và `$HOME/.local/state` theo trình tự và quay lại thư mục tạm thời khi không có thư mục người dùng. Nhiều phiên bản nên sử dụng các đường dẫn trạng thái độc lập.

Kết nối sản xuất sử dụng `wss://`. `ws://127.0.0.1` trong ví dụ chỉ dành cho xác minh cục bộ.

### Thông báo liên kết Agent

JSON có thể được đặt thành `"notify_urls": ["https://example.invalid/notify"]` hoặc sử dụng `remote-agent --notify-url https://example.invalid/notify` (có thể lặp lại); biến môi trường `REMOTE_NOTIFY_URL` chấp nhận các địa chỉ được phân tách bằng dấu phẩy. Danh sách biến môi trường/dòng lệnh không trống sẽ thay thế cấu hình JSON; mặt khác sử dụng JSON và cuối cùng quay trở lại UCI `remote-agent.main.notify_url` được phân tách bằng dấu phẩy chỉ trong OpenWrt. Tắt POST khi tất cả các nguồn bị bỏ qua hoặc trống.

`agent.connected` được gửi sau khi đăng ký thành công, `agent.disconnected` được gửi một lần khi phiên đã thiết lập kết thúc và chỉ `agent.rejected` được gửi khi đăng ký bị từ chối rõ ràng; lỗi mạng trước khi đăng ký sẽ không được thông báo. POST sử dụng các đối tượng JSON giống như Exchange Web Push (`title`, `body`, `url`, `event`, `device_id`, `occurred_at`), nhưng mô tả Liên kết agent thay vì trạng thái trực tuyến của thiết bị: `event` có tiền tố `agent.` thay vì `device.`, `url` là `""` (không có đường dẫn bảng điều khiển Web Push), `tag` không được gửi. Phần nội dung của lệnh ngắt kết nối và từ chối chứa bản tóm tắt ngắn gọn về lý do bảo mật và không bao gồm mã thông báo của thiết bị; thời gian là RFC3339 UTC.

Thông báo được gửi ở chế độ nền. Nếu thất bại, chỉ tên sự kiện được ghi lại, địa chỉ đích không được ghi lại và agent không bị ngăn kết nối lại. Chính sách thử lại tương tự như Exchange Web Push: tối đa 5 lần thử, lỗi mạng và HTTP 408/429/5xx có thể được thử lại, 404/410 và 4xx không thể thử lại khác sẽ bị loại bỏ; số giây `Retry-After` được ưu tiên, nếu không thì độ trễ theo cấp số nhân bắt đầu ở 5 giây và độ trễ được giới hạn trong 1–900 giây. Chỉ chấp nhận HTTP/HTTPS, không theo các chuyển hướng; không đặt tên người dùng, mật khẩu hoặc thông tin xác thực khác vào URL, địa chỉ chứa tên người dùng/mật khẩu sẽ bị từ chối.


### Proxy gửi đi và duy trì kết nối

Cả không có giao diện và `--ui` đều kết nối với Exchange thông qua cùng một `client::run`. WebSocket đọc các biến môi trường agent được kế thừa bởi quy trình agent: lấy máy chủ phù hợp `HTTPS_PROXY` / `https_proxy`, `ALL_PROXY` / `all_proxy`, `HTTP_PROXY` / `http_proxy`; `NO_PROXY` / `no_proxy` không trống đầu tiên bỏ qua proxy. WebSocket của `remote-cli` sử dụng các quy tắc tương tự (#36/#37, #39). Việc đăng nhập vào các dịch vụ tự khởi động hoặc chạy nền yêu cầu phải đặt các biến này trong môi trường đang chạy của chúng. Việc đặt chúng trong một thiết bị đầu cuối khác sẽ không được chuyển sang quá trình đã bắt đầu.

Khi WSS của thiết bị đi qua proxy, trước tiên nó sẽ thiết lập đường hầm **HTTP CONNECT**, sau đó sử dụng Rustls để kết nối với mục tiêu; ALPN của TLS ClientHello **chỉ khai báo `http/1.1`**, nhất quán với CLI. Việc đàm phán `h2` sẽ phá vỡ đường dẫn Nâng cấp HTTP/1.1 WebSocket hiện tại; khi bị kẹt trên kết nối trong proxy lớp Clash, bạn nên kiểm tra xem proxy có hỗ trợ CONNECT hay không và sử dụng bản dựng có bản sửa lỗi #39/#42. Cả thiết bị lẫn đường dẫn WebSocket của CLI đều không hỗ trợ SOCKS, vui lòng sử dụng proxy HTTP CONNECT (ví dụ: `HTTPS_PROXY=http://127.0.0.1:7890`).

Sau khi hoàn tất kết nối trực tiếp hoặc CONNECT, agent cho phép `TCP_NODELAY` và TCP duy trì sau khi không hoạt động trong 30 giây; nó cũng gửi Ping WebSocket cho mỗi nhịp JSON và xóa rõ ràng Pong được xếp hàng tự động sau khi nhận được Ping. Mỗi thiết lập kết nối và chờ phản hồi đăng ký đều có thời gian chờ là 45 giây; gửi và đăng ký sau khi xóa Pong có thời gian chờ là 30 giây. Để biết chi tiết, hãy xem [Duy trì hoạt động và hết thời gian chờ](/docs/vi/websocket-protocol.md#keepalive-và-thời-gian-chờ) (#42).

Bản dựng Windows bật `desktop-ui` dùng runtime Tokio đa luồng để chạy đồng thời axum, khay hệ thống cục bộ và WebSocket của thiết bị, kể cả khi chạy không có giao diện. Trên nền tảng khác bật tính năng này, giao diện cục bộ (chọn `--ui` hoặc khởi chạy không có đối số) cũng dùng đa luồng. Bản dựng không bật tính năng này và chế độ không có giao diện với cấu hình được chỉ định rõ trên nền tảng khác dùng `current_thread`, phù hợp với thiết bị nhỏ.

## Giao diện cấu hình Linux / Windows / macOS

Các bản dựng bật `desktop-ui` theo mặc định và các bản phát hành dành cho máy tính để bàn phải giữ lại giao diện người dùng cấu hình cục bộ. Chỉ sử dụng `--no-default-features` cho các máy chủ không có giao diện người dùng hoặc các bản dựng được nhúng. Trong khi được kết nối, máy chủ quản lý, ID thiết bị và đầu vào mã thông báo thiết bị sẽ bị ẩn; dừng truy cập từ xa sẽ hiển thị lại chúng. Trạng thái kết nối, cài đặt mạng LAN và bắt đầu đăng nhập vẫn hiển thị.

```sh
cargo build --locked -p agent --release
# Linux / macOS
./target/release/remote-agent --ui
```

```powershell
# Windows
.\target\release\remote-agent.exe --ui
```

Trên Linux / Windows / macOS, nếu khởi động không có tham số kết nối (ví dụ nhấp đúp `remote-agent.exe`) và bật `desktop-ui`: khi chưa có cấu hình dùng được, hệ thống ghi `config.json` mặc định (chỉ điền Exchange mặc định, để trống device_id/token) và mở trang cấu hình trong trình duyệt. Khi đã có cấu hình, Windows dùng khay hệ thống mà không buộc mở trình duyệt; Linux/macOS in URL cục bộ (macOS cũng có biểu tượng trên thanh menu). Khởi chạy tương tác không có đối số khi không bật `desktop-ui` sẽ tạo tệp cấu hình mặc định và in đường dẫn trước khi thoát, tránh chỉ hiển thị `Missing device_id`. OpenWrt / systemd / tác vụ theo lịch vẫn nên dùng `--config`, UCI hoặc `REMOTE_*`.

Khởi chạy đơn giản và `--ui` rõ ràng sử dụng cùng một quy tắc: chỉ mở trình duyệt mặc định khi cấu hình đã lưu thiếu `device_id`/`token` không trống. Các tác nhân được định cấu hình kết nối ở chế độ nền mà không cần mở trình duyệt khác khi đăng nhập. Windows giữ lại mục nhập khay để mở trang thủ công; Linux/macOS sử dụng URL cục bộ được in trong thiết bị đầu cuối. `--ui --no-open` luôn ngăn chặn việc mở tự động, kể cả lần thiết lập đầu tiên. `--ui-port 8765` đặt một cổng cố định; nếu không thì một cổng khả dụng sẽ được chọn ngẫu nhiên. Giao diện người dùng chỉ nghe trên `127.0.0.1`. Tất cả nội dung đều được nhúng, không có phông chữ hoặc CDN bên ngoài.

Khay hệ thống Windows và thanh menu macOS có các mục cấu hình, cài đặt trình bảo vệ màn hình, quản lý tác vụ và thao tác bảng nhớ tạm. Mục quản lý tác vụ mở trực tiếp bảng tác vụ cục bộ. Linux dùng trang cấu hình trong trình duyệt.

Trước tiên, hãy sử dụng `remote-cli device add <id>` để lấy ID và mã thông báo của thiết bị, sau đó điền vào máy chủ, ID thiết bị và mã thông báo thiết bị trong **Cài đặt kết nối**:

- **Chỉ lưu**: Lưu cấu hình mà không cần kết nối với máy chủ.
- **Lưu và kết nối**: Lưu và kết nối; khi cấu hình được sửa đổi trong quá trình hoạt động, kết nối cũ sẽ bị ngắt và cấu hình mới sẽ được sử dụng để kết nối.
- **Dừng truy cập từ xa**: Ngắt kết nối, hủy thực thi từ xa hiện tại và phiên cuối; cấu hình được giữ nguyên
- Thanh trạng thái hiển thị **Connected**, Đang kết nối, Đang kết nối lại hoặc Đã dừng sau khi đăng ký thành công và hiển thị lỗi kết nối mới nhất.

Khi bắt đầu kết nối, giao diện người dùng trước tiên sẽ ghi **Connecting** vào tệp trạng thái của chính nó, sau đó bắt đầu tác vụ `client::run` để tránh trang giữ nguyên giá trị mặc định khi tệp trạng thái chưa tồn tại (#42).

Mã thông báo đã lưu sẽ không được trả lại trang web. Để trống để giữ lại mã thông báo ban đầu. Cấu hình sử dụng thay thế nguyên tử tệp tạm thời, quyền Linux là `0600` và Windows ACL chỉ ủy quyền cho người dùng hiện tại. Khi tệp cấu hình JSON bị hỏng hoặc không thể đọc được, giao diện sẽ nhắc sửa chữa và tệp gốc sẽ không bị ghi đè. Tệp cấu hình tương tự chỉ có thể được mở bằng một quy trình UI.

Đường dẫn cấu hình mặc định là `$XDG_CONFIG_HOME/remote-agent/config.json` cho Linux/macOS (`~/.config/remote-agent/config.json` khi không được đặt) và `%LOCALAPPDATA%\remote-agent\config.json` cho Windows. `--ui --config <path>` hoặc `REMOTE_CONFIG` chỉ định tệp JSON hiện có. Giao diện người dùng không chấp nhận `--server`, `--device-id`, `--token`, `--state-file` và ghi đè biến môi trường tương ứng để tránh sự không nhất quán giữa giá trị đã lưu và kết nối thực tế. JSON `state_file` cũng áp dụng cho giao diện người dùng; khi vắng mặt, nó sử dụng mặc định liên tục tương tự như không có đầu để bảo vệ danh tính và sự tin cậy được niêm phong.

Trang cấu hình cung cấp công tắc **Start khi đăng nhập/Đăng nhập tự động khởi động** (`autostart: { supported, enabled, detail }` từ `GET /api/status`, `POST /api/autostart` từ `{ "enabled": true|false }`). Không yêu cầu đặc quyền nâng cao:

- **macOS**: Cài đặt/gỡ bỏ `~/Library/LaunchAgents/us.ivjn.remote-agent.plist` (mẫu giống với `install/macos/install.sh`), `launchctl bootstrap` / `bootout`; nhị phân được sao chép sang `~/.local/bin/remote-agent` trước tiên và cấu hình mặc định là `~/.config/remote-agent/config.json` (giữ lại nếu nó tồn tại).
- **Windows**: Đăng ký/hủy đăng ký tác vụ theo lịch trình của người dùng hiện tại `RemoteAgent` (AtLogOn), căn chỉnh logic `install/windows/install.ps1`, thư mục cài đặt `%LOCALAPPDATA%\remote-agent`.
- **Linux**: Cài đặt/gỡ bỏ các đơn vị hệ thống của người dùng (`install/linux/remote-agent.service`), `systemctl --user enable --now`/`disable`.

Khi được bật, tệp thực thi hiện tại sẽ được sao chép vào đường dẫn cài đặt đã thống nhất khi cần và `config.json` hiện có sẽ được giữ lại; phản hồi và nhật ký sẽ không in mã thông báo. Giao diện người dùng vẫn quản lý quy trình hiện tại; sau khi bật tính năng tự khởi động, LaunchAgent/tác vụ theo lịch trình/systemd sẽ hiển thị nền agent ở lần đăng nhập tiếp theo khi thoát khỏi giao diện người dùng.

**Việc đóng tab trình duyệt không dừng Agent; việc thoát khỏi quá trình Agent sẽ dừng kết nối của nó. Khởi động giao diện người dùng tự động kết nối một thiết bị đã được định cấu hình; thiết lập đầu tiên yêu cầu Lưu và kết nối.** Giao diện người dùng quản lý quy trình hiện tại và có thể bật hoặc tắt tính năng tự động khởi động đăng nhập. Dừng dịch vụ nền hiện có trước khi bắt đầu một quy trình giao diện người dùng khác cho cùng một thiết bị. Sau khi cấu hình và bật tính năng tự khởi động, bạn có thể đóng tab trình duyệt. Các hoạt động Remote sử dụng quyền của tài khoản đang chạy Agent.

URL gốc chứa khóa truy cập ngẫu nhiên cho giao diện người dùng này, không chia sẻ nó. API cũng xác minh Máy chủ và Nguồn gốc cũng như từ chối cuộc gọi từ các trang web bên ngoài. Mã thông báo thiết bị được lưu trữ trong tệp cấu hình và không được lưu trữ dưới dạng mã hóa; nó vẫn có thể được đọc bởi tài khoản hiện tại và quản trị viên của máy. Bản dựng rút gọn không có tính năng `desktop-ui` vẫn duy trì hoạt động dòng lệnh ban đầu và OpenWrt và Android sử dụng các giao diện hiện có tương ứng của chúng.

## BSD miễn phí

Trang web chính thức cung cấp `remote-freebsd-x64.tar.gz` và `remote-freebsd-arm64.tar.gz` (biên dịch chéo với `cargo zigbuild` trên Linux và liên kết tới FreeBSD 14 libc). Sau khi giải nén:

```sh
mkdir -p ~/.local/bin
cp remote-agent remote-cli ~/.local/bin/
sudo remote-agent install --system --device-id NAME --token TOKEN
```

Nguồn hiện tại hỗ trợ cài đặt hệ thống RC.d, số liệu sysctl, tổng số giao diện `netstat -ibn`, chỉ số quạt/nhiệt độ FreeBSD và NetBSD `envstat`. Điều khiển máy tính để bàn X11 được quảng cáo khi có sẵn `DISPLAY`, `python3` và `xdotool`; ảnh chụp màn hình cũng cần Gối. Cài đặt phương tiện và hệ thống vẫn không khả dụng. Tự cập nhật nhận dạng các kho lưu trữ BSD x64/arm64 khi có gói được xác minh phù hợp tồn tại trong bảng kê khai phát hành. Những thay đổi này yêu cầu các gói được xây dựng mới; Xác minh thời gian chạy BSD gốc vẫn cần thiết.

## NetBSD

Trang web chính thức cung cấp `remote-netbsd-x64.tar.gz` (amd64, sử dụng zig link NetBSD 10 libc biên dịch chéo trên Linux, phụ thuộc động chỉ là libc/libpthread/libutil). Cách sử dụng giống như FreeBSD:

```sh
mkdir -p ~/.local/bin
cp remote-agent remote-cli ~/.local/bin/
sudo remote-agent install --system --device-id NAME --token TOKEN
```

Khả năng: kết nối với Exchange, `exec`/PTY/truyền tệp; các chỉ báo đi qua sysctl (`kern.cp_time`, `hw.physmem64`, `vm.uvmexp2`); Kiểm soát GUI trên máy tính để bàn được triển khai dưới dạng sơ khai, chưa được công bố. Nền tảng thiết bị báo cáo nó là `netbsd`. Tính năng tự cập nhật và khởi động lại RC.d hoạt động như trên FreeBSD (`remote-netbsd-x64.tar.gz` / `remote-netbsd-arm64.tar.gz`), miễn là bảng kê khai liệt kê kho lưu trữ.

## Gói OpenWrt chính thức 25.12

Trang web chính thức cung cấp các gói `remote-agent` được xây dựng bằng SDK OpenWrt 25.12.5 chính thức (apk-tools, phiên bản gói `0.1.29-r1`, bao gồm agent 0.1.29):

| mục tiêu | kiến trúc gói | tập tin |
|---|---|---|
| ipq40xx/chung | arm_cortex-a7_neon-vfpv4 | `remote-agent-openwrt-ipq40xx-official.apk` |
| x86/64 | x86_64 | `remote-agent-openwrt-x86_64-official.apk` |
| vũ khí/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` |
| đường dốc/mt7621 | mipsel_24kc | `remote-agent-openwrt-ramips-mt7621-official.apk` |
| ath79/chung | mips_24kc | `remote-agent-openwrt-ath79-official.apk` |

Giao diện LuCI phổ biến: `luci-app-remote-openwrt-official.apk`. Sau khi kiểm tra kiến ​​trúc với `apk --print-arch` `apk add --allow-untrusted ./文件.apk`. Gói MIPS cũng phụ thuộc vào `libgcc1` (hình ảnh chính thức đi kèm theo mặc định).

## Linux xạ hương (Alpine) và arm64

- `remote-linux-arm64.tar.gz`: aarch64, glibc 2.28+ (Debian 10+/Ubuntu 18.04+/RHEL 8+, hệ thống Raspberry Pi 64-bit, v.v.), hỗ trợ đăng ký `remote-agent install` với systemd và tự cập nhật.
- `remote-linux-musl-x64.tar.gz` / `remote-linux-musl-arm64.tar.gz`: musl được liên kết tĩnh cho các bản phân phối không có glibc như Alpine. Alpine không có systemd theo mặc định; sử dụng `remote-agent install --no-start` để viết cấu hình, sau đó thêm dịch vụ OpenRC của riêng bạn (`/etc/init.d/remote-agent`) hoặc chạy nó ở nền trước. Các bản dựng musl hỗ trợ `update self` và tự động cập nhật máy tính để bàn và chỉ cài đặt kho lưu trữ musl; sau bản cập nhật một lần, họ thử systemd, rồi OpenRC (`rc-service remote-agent restart`), sau đó khởi động lại quá trình. Kho lưu trữ musl trên máy chủ OpenWrt vẫn cập nhật thông qua opkg/apk.

## Linux

Nên sử dụng lệnh con tích hợp sẵn (sẽ sao chép tệp thực thi hiện tại, ghi cấu hình riêng và đăng ký systemd; không dựa vào các tệp `install/` trong kho lưu trữ):

```sh
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 同时删二进制与配置
```

Các bước thủ công tương đương (vẫn có sẵn):

```sh
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
```

Dịch vụ người dùng chạy theo mặc định với phiên đăng nhập. Khi cần khởi động và cư trú, quản trị viên có thể thực thi `loginctl enable-linger <user>` hoặc sử dụng đơn vị hệ thống `--system` ở trên (`RuntimeDirectory=remote-agent`, `WantedBy=multi-user.target`). Các lệnh Remote sử dụng quyền tài khoản dịch vụ. Việc dừng dịch vụ sẽ chấm dứt tất cả các tiến trình trong dịch vụ. `install/linux/install.sh` là lớp bao bọc mỏng xung quanh `remote-agent install --force`.

## Windows

Sau khi cài đặt chuỗi công cụ Rust MSVC và Công cụ xây dựng Visual Studio C++:

```powershell
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` sao chép tệp nhị phân và cấu hình sang `%LOCALAPPDATA%\remote-agent`, hạn chế ACL cấu hình và đăng ký tác vụ theo lịch trình `RemoteAgent` bắt đầu khi đăng nhập. Theo mặc định, nếu tác vụ hoặc tệp đích đã tồn tại thì sẽ có thông báo lỗi; `--force` trước tiên sẽ dừng và hủy đăng ký tác vụ cũ, sau đó ghi đè lên nó. Chế độ này yêu cầu người dùng đăng nhập; nó không phải là dịch vụ hệ thống SCM Windows. `install/windows/install.ps1` là một lớp bao bọc mỏng xung quanh `remote-agent install` (có thể thêm `-Force`).

Nếu bạn sử dụng giao diện người dùng để tạo cấu hình trước, `--config` có thể trỏ đến một đường dẫn riêng (chẳng hạn như tệp do `--ui --config .\device-config.json` tạo).

Các lệnh Windows sử dụng cú pháp PowerShell, chẳng hạn như `Get-Process` và đầu ra được gửi ở dạng UTF-8. Mỗi phiên thực thi và thiết bị đầu cuối có một Đối tượng Công việc độc lập; cây tiến trình con của nó được dọn sạch khi hết thời gian chờ, hủy, ngắt kết nối hoặc thoát agent.

## macOS

Yêu cầu chuỗi công cụ Rust (`rustup`, với mục tiêu `aarch64-apple-darwin` hoặc `x86_64-apple-darwin` mặc định). Các chỉ báo được thu thập bởi sysctl, `getloadavg(3)` và mach `host_statistics`, đồng thời thông tin tĩnh (model, kernel, phiên bản hệ thống) được thu thập bởi `sysctl` và `sw_vers`; nền tảng báo cáo nó là `macos`.

Cảm biến theo yêu cầu (không phải nhịp tim) cố gắng hết sức để thu thập mà không cần sudo hoặc phụ thuộc SMC bổ sung: Pin `pmset -g batt` (máy tính xách tay), CPU_Speed_Limit / Bộ lập lịch / CPU có sẵn và văn bản áp suất nhiệt của `pmset -g therm`, AppleSmartBattery `Temperature` của `ioreg` và quạt RPM (nếu chương trình cơ sở bị lộ). Máy tính để bàn (như Mac mini) thường không có pin; Apple Silicon cũng có thể không có CPU_Speed_Limit bằng số. Nhiệt độ gần/chế độ CPU thô yêu cầu IOKit/SMC riêng, hiện tại chưa cạo. Nếu áp suất nhiệt, điều tiết, hoặc bài đọc của người hâm mộ có sẵn, Agent không đánh dấu `thermal` là không khả dụng. Sau khi kích hoạt `desktop-ui`, bạn có thể sử dụng `remote-agent --ui` để định cấu hình và chuyển đổi tính năng tự động khởi chạy đăng nhập (LaunchAgent).

```sh
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` Sao chép tệp thực thi hiện tại sang `~/.local/bin/remote-agent`, ghi `~/.config/remote-agent/config.json` (quyền 600), tạo `~/Library/LaunchAgents/us.ivjn.remote-agent.plist` và thực thi `codesign -s - --force` trên tệp nhị phân (macOS sẽ hủy thay thế tại chỗ không dấu), sau đó `launchctl bootstrap` / `kickstart`. `RunAtLoad` + `KeepAlive` cho phép agent khởi động ngay sau khi đăng nhập hoặc được kéo lên bằng launchd sau khi gặp sự cố hoặc ngắt kết nối. Nhật ký được ghi vào `~/Library/Logs/remote-agent.log`. `install/macos/install.sh` là một lớp bọc mỏng. `$HOME/.local/state/remote-agent/agent.state` được sử dụng khi không có tệp trạng thái nào được chỉ định. Các lệnh Remote chạy với sự cho phép của người dùng đã đăng nhập đó.

Dừng/Gỡ cài đặt:

```sh
remote-agent uninstall
# 或手动：
# launchctl bootout gui/$(id -u)/us.ivjn.remote-agent
# rm ~/Library/LaunchAgents/us.ivjn.remote-agent.plist
```

Khi cần khởi động (khi không có ai đăng nhập) và ở đó, hãy sử dụng LaunchDaemon trong `/Library/LaunchDaemons` và tải nó dưới dạng root; ở chế độ này, các lệnh từ xa chạy với quyền root, vui lòng đánh giá rủi ro tương ứng.

## APK độc lập Android

Dự án phía thiết bị có tại `android-agent/`. `mobile/` vẫn là khách hàng vận hành và bảo trì. Cả hai có thể được cài đặt riêng mà không cần Expo Go, Termux hoặc root.

Địa chỉ mặc định của Agent cho tất cả các nền tảng là `wss://exchange.ivjn.us`. Android có thể nhấp vào **Kết nối bằng mã sáu chữ số** và quản trị viên CLI sẽ nhập mã sáu chữ số để ủy quyền và tự động lấy danh tính thiết bị; Agent gốc (trang địa phương Windows/macOS, OpenWrt LuCI "Thêm thiết bị", dòng lệnh `remote-agent pair` / `remote-agent join <邀请>`) có thể hiển thị mã sáu chữ số và mã QR cho Console để quét và phê duyệt hoặc dán lời mời một lần để tham gia. Xem [Khởi tạo và ghép nối](/docs/vi/pairing-and-enrollment.md) để biết chi tiết.

### Kết nối đầu tiên và màn hình chính

1. Mở **Remote Agent** trên thiết bị Android mà bạn muốn quản lý. Để thiết lập lần đầu, hãy chọn **Kết nối bằng mã gồm sáu chữ số** hoặc **Quét kết nối mã QR** và nhận được sự chấp thuận của quản trị viên. Xem [Khởi tạo và ghép nối](/docs/vi/pairing-and-enrollment.md) để biết quy trình làm việc về tài khoản, mã QR và mã sáu chữ số.
2. Nếu bạn đã có ID thiết bị và mã thông báo thiết bị, hãy mở **Cài đặt → Cài đặt kết nối**, nhập máy chủ và thông tin xác thực rồi chọn **Lưu và kết nối**. Các trường thông tin xác thực bị ẩn khi kết nối được bật. Ngắt kết nối khỏi màn hình chính trước khi thay đổi nhận dạng thiết bị.
3. Màn hình chính hiển thị trạng thái kết nối, điều khiển chia sẻ màn hình và danh sách người quản lý. Chọn **Connect device** khi cấu hình được lưu nhưng kết nối bị tắt. Việc mở lại ứng dụng sẽ khôi phục kết nối đã bật.

Android 12 trở lên sử dụng màn hình giật gân của hệ thống; Android 8–11 sử dụng nền khởi động có màu sắc thương hiệu phù hợp. Ứng dụng sẽ mở màn hình chính khi khung đầu tiên của nó sẵn sàng mà không cần chờ kết nối mạng hoặc thêm thời lượng giật gân tối thiểu. Quá trình chuyển đổi bị bỏ qua khi hoạt ảnh hệ thống bị tắt. Tải cài đặt theo yêu cầu; việc mở màn hình chính sẽ không cấp quyền truy cập hoặc chia sẻ màn hình. Điều này mô tả hành vi nguồn hiện tại; kiểm tra phiên bản đã cài đặt trước khi mong đợi nó được tải xuống công khai.

### Hoạt động nền, thông báo và dừng

Việc di chuyển ứng dụng sang nền sau hoặc đóng giao diện của ứng dụng sẽ không dừng dịch vụ trên nền trước. Agent kết nối lại sau khi mất mạng. Khi kết nối được bật, khởi động và mở khóa thiết bị hoặc cập nhật gói ứng dụng, hãy kích hoạt nỗ lực khôi phục dịch vụ. Sau khi buộc dừng, hãy mở lại ứng dụng theo cách thủ công. Chính sách về pin của nhà sản xuất cũng có thể yêu cầu quyền chạy ở chế độ nền.

Khi cho phép thông báo, hãy chọn **Quản lý kết nối** để quay lại ứng dụng. Trong khi chia sẻ, thông báo đưa ra **Dừng chia sẻ**, thao tác này chỉ kết thúc việc chia sẻ màn hình. Để ngắt kết nối truy cập từ xa và tắt khôi phục tự động, chọn **Disconnect** trên màn hình chính và xác nhận. Cấu hình đã lưu vẫn có sẵn cho thiết bị **Connect** sau này. Thao tác **Stop màn hình chính** cũng kết thúc việc chia sẻ mà không ngắt kết nối quyền truy cập từ xa.

Mã thông báo thiết bị được mã hóa bằng Android Keystore AES-GCM. Cấu hình ứng dụng bị loại trừ khỏi bản sao lưu và Hoạt động hoặc Dịch vụ đã xuất không chuyển mã thông báo. Các lệnh Remote chạy trong UID ứng dụng APK, bị ràng buộc bởi hộp cát Android và SELinux. Điều này không cấp quyền truy cập vào dữ liệu cũng như quyền root của ứng dụng khác; cài đặt hệ thống, khả năng truy cập và chia sẻ màn hình cần có sự cho phép của chính họ.

Trong khi quyền truy cập từ xa được bật, dịch vụ trên nền trước sẽ giữ các khóa đánh thức CPU và màn hình, giữ màn hình ở chế độ nền và trong khi kết nối lại với chi phí thêm nguồn điện. Việc ngắt kết nối hoặc hủy dịch vụ sẽ giải phóng các khóa và khôi phục hành vi hết thời gian chờ màn hình hệ thống bình thường. Nút nguồn vẫn khóa máy bình thường và không bỏ qua xác thực. Thông báo hiển thị trạng thái kết nối hoặc chia sẻ; màn hình chính cung cấp hành động ngắt kết nối.

### Xây dựng từ nguồn

Bắt đầu mỗi ví dụ Gỡ lỗi hoặc Phát hành bên dưới từ thư mục gốc của kho lưu trữ. Kiểm tra danh sách thiết bị trước khi cài đặt và thay thế DEVICE_SERIAL bằng sê-ri thiết bị dự định. Bao bì, kiểm tra đơn vị và Lint đầy đủ là các kiểm tra riêng biệt. Nếu Lint không thành công, hãy kiểm tra báo cáo và khắc phục hoặc ghi lại nguyên nhân; bỏ qua nhiệm vụ không được tính là vượt qua nó.

Bản dựng yêu cầu JDK 17+, Android SDK Platform 36, NDK `28.2.13676358`, Python 3 và Rust. Đặt `ANDROID_HOME` (hoặc `ANDROID_SDK_ROOT`); đường dẫn NDK tùy chỉnh sử dụng `ANDROID_NDK_HOME`. Hiển thị toàn màn hình của `.Aran` phụ thuộc vào Metafile Android SDK: đặt kho lưu trữ `Metafile` trong cùng thư mục với `remote-control` (Gradle tham chiếu mô-đun `:aran` thông qua `../../Metafile/android-sdk/aran`).

```sh
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 tự động gọi `scripts/build-android-agent.py` để tải hai tệp thực thi ABI Rust vào thư mục thư viện gốc APK. Sau khi cài đặt, hãy bắt đầu trực tiếp từ thư mục chỉ đọc này, thư mục này tuân thủ các hạn chế của Android đối với thư mục dữ liệu ứng dụng cấm thực thi các tệp. APK gỡ lỗi được sử dụng để xác minh quá trình phát triển; Bản phân phối chính thức phải sử dụng cấu hình chữ ký riêng của tổ chức để xây dựng bản phát hành và khóa chữ ký được giữ lại để hỗ trợ các bản nâng cấp tiếp theo.

Bản phát hành có nội dung `android-agent/keystore.properties` (đã bị Git bỏ qua) hoặc cấu hình bên ngoài được chỉ ra bởi biến môi trường `REMOTE_AGENT_SIGNING_PROPERTIES`. Các trường xem `android-agent/keystore.properties.example`; các khóa nên được giữ bên ngoài kho lưu trữ. Bản dựng bản phát hành sẽ không thành công khi thiếu cấu hình chữ ký và sẽ không quay lại ký Debug.

```powershell
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
```

Vui lòng sao lưu khóa phát hành và mật khẩu của nó một cách an toàn. Các phiên bản tiếp theo phải sử dụng cùng một khóa để nâng cấp. Chữ ký gỡ lỗi và phát hành là khác nhau. Không thể ghi đè trực tiếp các bản cài đặt Gỡ lỗi hiện có; việc gỡ cài đặt sẽ xóa cấu hình cục bộ và thông tin đăng nhập của thiết bị, đồng thời việc ghép nối lại phải được sắp xếp trước. `scripts/build-site-downloads.py` sử dụng APK phát hành đã ký làm gói tải xuống Agent. Việc ký phát hành không đảm bảo rằng Play Protect sẽ cho phép APK.

### Android quét mã để kết nối

**Quét mã QR kết nối** trên trang chủ Remote Agent sử dụng camera điện thoại để nhận dạng mã QR kết nối ngoại tuyến mà không cần dịch vụ Google Play. Cho phép truy cập camera khi sử dụng lần đầu; việc hủy quét mã QR sẽ không sửa đổi cấu hình. Sau khi quét thành công mã QR, ID máy chủ và thiết bị sẽ được hiển thị và mã thông báo sẽ vẫn bị ẩn. Nhấp vào **Connect** trước khi mã hóa, lưu cấu hình và bắt đầu kết nối; nếu một kết nối hiện có đang chạy thì nó phải được dừng trước khi có thể nhập cấu hình mới. Quét mã QR không tự động bật các điều khiển màn hình hoặc tính năng trợ năng.

Trước tiên, hãy đăng ký một thiết bị độc lập trong Exchange và nhận mã thông báo thiết bị ****, sau đó tạo mã QR cục bộ trên máy tính của bạn (không sử dụng mã thông báo tài khoản vận hành và bảo trì hoặc trang web mã QR trực tuyến của bên thứ ba):

```powershell
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 从标准输入读取。
```

Mở SVG đã tạo trên máy tính của bạn và để Remote Agent quét nó. Tệp mã QR chứa mã thông báo thiết bị và phải được giữ riêng tư và xóa sau khi sử dụng cũng như không gửi tới Git. Công cụ từ chối ghi đè lên các tập tin hiện có.

Nội dung mã QR là UTF-8 JSON, có tổng chiều dài lên tới 2048 byte:

```json
{"type":"remote-agent","version":1,"server":"wss://exchange.example.com","device_id":"android-phone","token":"DEVICE_TOKEN"}
```

Quét mã QR chỉ chấp nhận địa chỉ `wss://`, có thể bao gồm đường dẫn `/ws/device` và không chấp nhận thông tin người dùng URL, tham số truy vấn hoặc đoạn. `server`, `device_id` và `token` phải là các chuỗi không trống, có tối đa 512, 128 và 512 ký tự tương ứng và không được chứa các ký tự điều khiển hoặc khoảng trống ở đầu và cuối. Cấu hình thủ công vẫn có sẵn cho môi trường thử nghiệm `ws://` cục bộ.

### Android Khả năng truy cập, Ảnh chụp màn hình và Nhập văn bản

Sử dụng `remote-cli` cho các lệnh sau. Thiết bị báo cáo khả năng `android_control` thông qua cầu nối APK; kiểm tra `status` để biết các quyền được cấp.

1. Kết nối thiết bị, sau đó mở **Cài đặt → Màn hình và tương tác** rồi bật **Cho phép điều khiển màn hình từ xa** (tắt theo mặc định).
2. Chọn **Cài đặt khả năng truy cập** và bật Remote Agent trong cài đặt Android. Điều này cho phép đọc cây giao diện người dùng có thể truy cập, nhấn, vuốt, điều hướng hệ thống và thay thế văn bản trong trường nhập tập trung.
3. Android 11+ có thể chụp ảnh màn hình thông qua hỗ trợ trợ năng được bật. Trên Android 8–10 hoặc chỉ để chụp màn hình, hãy chọn **Chia sẻ màn hình** trên màn hình chính hoặc **Chia sẻ màn hình tiêu chuẩn** trong cài đặt và xác nhận hộp thoại chia sẻ màn hình của Android. Việc ủy ​​quyền phải được gia hạn sau khi phiên chia sẻ kết thúc hoặc quá trình khởi động lại.

```sh
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` Sử dụng Android `ACTION_SET_TEXT` **Thay thế tất cả văn bản trong hộp nhập hiện được tập trung**, hỗ trợ tiếng Trung, biểu tượng cảm xúc và nhiều dòng, tối đa 8192 byte UTF-8, không cho phép NUL. Trước tiên hãy nhấp vào hộp nhập có thể chỉnh sửa; các điều khiển không được tập trung, không thể chỉnh sửa hoặc không hỗ trợ hành động này sẽ trả về lỗi. `--stdin` đọc UTF-8 và giữ nguyên dòng mới, đồng thời tránh đưa văn bản vào lịch sử dòng lệnh. `key` hỗ trợ `back`, `home`, `recents`, `notifications`, `quick-settings`.

`xml` trả về cây giao diện `<hierarchy><node ...>` lồng nhau, bao gồm `resource-id`, `text`, `content-desc`, `bounds` và trạng thái điều khiển; tối đa 200 nút, 50 lớp và tối đa 256 ký tự thuộc tính, cả phản hồi và phần tử gốc sẽ cho biết có nên cắt bớt hay không. `click` là đối sánh chính xác bằng cách sử dụng các giá trị đầy đủ của các thuộc tính này (không phải XPath): `--view-id` tương ứng với `resource-id`, `--description` tương ứng với `content-desc`; nhiều điều kiện được kết hợp bởi AND. Chỉ các điều khiển hiển thị và kích hoạt mới được khớp. Không hoặc có nhiều kết quả trùng khớp sẽ dẫn đến lỗi; nhấp chuột sẽ không được thực hiện nếu quá trình truyền tải vượt quá giới hạn. Nhãn văn bản sẽ tìm kiếm điều khiển dành cho phụ huynh có thể nhấp vào. Nếu không bấm được, hãy sử dụng `tap X Y`. XML phải được lấy lại sau khi thay đổi giao diện và thay vào đó, các thuộc tính dài có thể được định vị bằng ID tài nguyên.

Để nhập phương thức nhập, hãy mở **Settings → Remote input**. Chọn **Enable Remote input** để bật Remote Đầu vào trong Android, sau đó **Chọn phương thức nhập** để chọn. Sau khi tập trung vào hộp nhập ứng dụng đích, `type` chèn văn bản vào con trỏ/lựa chọn thông qua `InputConnection.commitText`, hỗ trợ tiếng Trung, Biểu tượng cảm xúc và nhiều dòng, đồng thời sử dụng cùng giới hạn 8192 byte UTF-8 như `text`. Nó sẽ không tự động nhấp để gửi hoặc gửi biểu mẫu cũng như không thể sửa đổi cấu hình của Agent. **Switch keyboard** trên bàn phím sẽ khôi phục phương thức nhập hàng ngày của bạn. `status` trả về `ime_enabled`, `ime_selected` và `ime_connected`. Màn hình phải được bật và máy phải được mở khóa trước khi đọc và nhấn vào giao diện hoặc nhập văn bản.

Trang **Device Screen** của Remote Console cung cấp các nhấp chuột tọa độ, thu thập XML và nhấp chuột thuộc tính cũng như nhập văn bản đầu vào Remote. Các quyền không khả dụng hoặc trạng thái màn hình sẽ vô hiệu hóa nút này; Việc nhập thành công sẽ xóa văn bản và ảnh chụp màn hình, XML và nội dung cần nhập sẽ bị xóa khi trang chuyển sang chế độ nền.

### Khả năng hệ thống

Cài đặt hệ thống hoạt động theo nhà cung cấp được thiết bị báo cáo và không gian tên là một chuỗi chứ không phải bảng liệt kê được mã hóa cứng. Android vẫn sử dụng khả năng `android_settings`, được cung cấp bởi `system`, `secure`, `global`. Khả năng sử dụng Linux (không có OpenWrt) và macOS `system_settings`: gsettings cho người dùng hiện tại trên Linux, `/usr/bin/defaults` cho người dùng hiện tại trên macOS. Tiện ích mở rộng trình duyệt sử dụng khả năng `browser_settings` để đọc và ghi `privacy.network`, `privacy.services` và `privacy.websites` thông qua phiên được mã hóa. Windows và OpenWrt không cung cấp bộ cài đặt này. Khi thêm cài đặt giá trị khóa mới, chỉ cần đăng ký nhà cung cấp trong Agent hoặc tiện ích mở rộng và toán tử sẽ hiển thị theo danh sách.

Ba không gian tên của Android không liên quan gì đến chuyển đổi màn hình, hiển thị và trạng thái màn hình khóa và được vận hành thông qua ContentResolver thay vì nối shell. Để ghi cài đặt `system` từ APK thông thường, bạn cần nhấp vào **Cho phép sửa đổi cài đặt hệ thống** trên Agent và ủy quyền cho nó; để ghi `secure/global`, bạn cần có thêm quyền hệ thống `WRITE_SECURE_SETTINGS`. Trạng thái trả về các quyền hiện tại và `providers`, đồng thời Android và các chính sách của nhà sản xuất vẫn có thể từ chối việc đọc và ghi riêng lẻ.

`settings list --prefix` Tên cài đặt bộ lọc trên Android; trên Linux/macOS, không có tiền tố chỉ trả về lược đồ hoặc tên miền, có tiền tố trả về chuỗi cấp cao nhất, số nguyên và giá trị Boolean của lược đồ hoặc miền này. `settings clear` Hiện tại chỉ chấp nhận các mục về quyền riêng tư của trình duyệt, các nhà cung cấp khác sẽ trả về các lỗi rõ ràng. Quá trình kiểm tra ghi lại không gian tên, tên cài đặt và số ký tự, nhưng không ghi lại giá trị.

```sh
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
```

Giá trị của `settings set` là một chuỗi và `--stdin` giữ lại các dòng mới; giá trị cài đặt không được ghi vào cuộc kiểm tra. Giá trị có thể lên tới 8192 byte UTF-8, không cho phép NUL; tên cài đặt có thể lên tới 256 ký tự ASCII (chữ cái, số, dấu gạch dưới, dấu chấm, dấu hai chấm, dấu gạch ngang). Danh sách có tối đa 500 mặt hàng và 512 KiB. Nếu vượt quá giới hạn, `truncated` sẽ được trả về; giá trị đơn tối đa là 64 KiB. Cài đặt không tồn tại trả về `value: null`. Giao diện sẽ không âm thầm buộc sửa đổi các cài đặt bị hạn chế. Nó sẽ được đọc lại để xác nhận sau mỗi lần ghi và sẽ trả về một lỗi rõ ràng nếu thất bại. Việc sửa đổi sẽ có hiệu lực vĩnh viễn và giá trị cũ sẽ không được khôi phục khi Agent bị ngắt kết nối. Lệnh phím tắt `screen-timeout` sử dụng giây (15–86400) và `screen_off_timeout` cơ bản sử dụng mili giây; tự động tắt màn hình và độ trễ trước khi khóa sau khi tắt màn hình là các cài đặt riêng biệt.

### Phát lại phương tiện (phương tiện truyền thông)

Việc đọc, âm thanh, hình ảnh (bao gồm cả văn bản toàn màn hình), video và trang web được hợp nhất với khả năng của `media` và được thông báo trên các Đại lý Android và Windows/Linux/macOS; OpenWrt không được thông báo. Giao thức chỉ có ba hành động: `media_play` (với đối tượng `media`), `media_stop` và `media_status`, cả ba đều trả về cùng một trạng thái `media`.

| `media.type` | trường | Kênh |
| --- | --- | --- |
| `speech` | `text`, `language` tùy chọn (BCP-47, chẳng hạn như `zh-CN`) | Âm thanh |
| `audio` | `url` hoặc `path` Chọn một, tùy chọn `loop` | Âm thanh |
| `image` | Chọn một trong các `url`, `path`, `text`, `p2p` tùy chọn (không thể mang theo `text`) | Màn hình |
| `video` | `url` hoặc `path` Chọn một, tùy chọn `p2p` | Màn hình |
| `web` | `url` (chỉ https) | Màn hình |

Có hai kênh độc lập trên thiết bị: **Sound** (đọc, âm thanh) và **Screen** (hình ảnh, văn bản, video, trang web). Nội dung mới chỉ thay thế nội dung hiện đang phát trên cùng một kênh, do đó, nhạc nền có thể được phát cùng lúc với băng chuyền hình ảnh. `channel` của `media_stop` có thể là `sound`, `screen` hoặc `all` (mặc định).

```sh
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` trả về:

```json
{"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` là mục mới nhất trong kênh âm thanh (được giữ lại sau khi kết thúc để dễ xem kết quả) và trạng thái là `queued` / `speaking` / `loading` / `playing` / `completed` / `stopped` / `error`; `null` khi chưa từng chơi. `screen` chỉ xuất hiện khi màn hình đang hiển thị, nếu không thì là `null`. `speech_available=false` có nghĩa là không có công cụ phát âm nào trên thiết bị. Âm lượng phương tiện chỉ có giá trị trên Android và bằng 0 trên máy tính để bàn.

Xác minh và kiểm tra: Văn bản (đọc hoặc văn bản hình ảnh) có thể lên tới 4000 byte UTF-8 và không cho phép NUL; URL phải là `https`, tối đa 2048 byte và không được chứa khoảng trống hoặc ký tự điều khiển; đường dẫn có thể lên tới 4096 byte và không thể chứa các ký tự điều khiển. Quá trình kiểm tra chỉ ghi lại loại, danh mục nguồn (`url` / `path` / `text`), số ký tự, có vòng lặp hay không và có sử dụng P2P hay không, không bao giờ URL, đường dẫn hoặc nội dung (URL có thể có mã thông báo ký). Tài khoản `readonly` không thể gọi bất kỳ hoạt động phương tiện nào.

#### Âm thanh: Đọc và âm thanh

- Nói to: Android sử dụng công cụ TTS của hệ thống; Windows sử dụng `System.Speech`; macOS sử dụng `say`; Linux sử dụng `spd-say` / `espeak-ng` / `espeak` (khi `speech_available=false` chưa được cài đặt, Nói to sẽ trả về lỗi rõ ràng). Máy tính để bàn sẽ cố gắng hết sức để ánh xạ các thẻ ngôn ngữ thành giọng nói địa phương. Android không quay trở lại cho đến khi động cơ bắt đầu phát ra âm thanh; trả về lỗi khi khởi tạo công cụ hệ thống hoặc không có dữ liệu ngôn ngữ; dừng Agent sẽ giải phóng TTS.
- Âm thanh: Nguồn là đường dẫn tuyệt đối trên thiết bị hoặc URL https. Đối với các tệp không có trên thiết bị, hãy sử dụng `remote-cli files upload` để tải chúng lên trước rồi phát theo đường dẫn. `loop` Lặp lại cho đến khi dừng.
  - Android sử dụng `MediaPlayer` với kênh âm lượng phương tiện và truyền trực tiếp URL. Nó sẽ không quay trở lại cho đến khi âm thanh bắt đầu. Nếu nó không khởi động được trong vòng 10 giây, một lỗi sẽ được trả về.
  - Trước tiên, phía máy tính để bàn tải URL xuống thư mục trạng thái tạm thời của người dùng hiện tại (tối đa 50 MB, chuyển hướng chỉ cho phép https) và sẽ xóa URL đó sau khi phát, dừng hoặc gặp lỗi; giao diện sẽ trở lại sau khi quá trình phát lại được bắt đầu và việc không tải xuống hoặc giải mã sẽ được phản ánh bởi `media status` và `sound.state=error`. Sử dụng `MediaPlayer` cho Windows (hỗ trợ mp3, wav, m4a, aac, wma, flac); sử dụng `afplay` cho macOS; tìm kiếm theo trình tự Linux `mpv`, `ffplay`, `paplay`, `pw-play`, `aplay`, sẽ trả về lỗi nếu không cài đặt gì (`paplay` / `aplay` không hỗ trợ mp3).
  - Đường dẫn phải là một tệp thông thường hiện có và có thể đọc được; Windows từ chối các đường dẫn mạng như `\\server\share` để tránh gửi thông tin xác thực đến máy chủ SMB từ xa.
- Không thể nghe thấy âm thanh khi âm lượng của thiết bị bằng 0.

#### Màn hình: hình ảnh, văn bản, video và trang web

- **image**: URL `path` gốc, `https` hoặc `text` (văn bản UTF-8 lớn trên nền tối, được coi là hình ảnh tĩnh). Các nguồn có phần mở rộng `.Aran` trên Android sẽ được giải mã bằng Metafile Android SDK trước khi được hiển thị.
- **video**: URL `path` hoặc `https` cục bộ, được phát theo vòng lặp. Không thể sử dụng `.Aran` trên Android làm luồng phương tiện HTTPS thông thường: khi không được lưu vào bộ đệm, nó sẽ được giải mã theo khối và phát trong khi tải xuống, đồng thời văn bản thuần túy được ghi vào bộ đệm cục bộ để phát lại trực tiếp vào lần tiếp theo. Địa chỉ khối HTTP là `/meta/<SHA1>` trong đường dẫn gốc gốc; các tệp gốc đọc thư mục `meta/` bên cạnh tệp kê khai. Mặc định sử dụng khóa được nhúng trong tệp kê khai hoặc khóa mặc định SDK. P2P tùy chọn: Chuỗi nguồn khối là `meta/` cục bộ, UDP P2P (`LOOKUP_ONLY`, chưa được đăng ký trong metastore), dự phòng HTTP. Dịch vụ khám phá và ngang hàng tĩnh được cung cấp bởi `media.p2p`, chẳng hạn như `--p2p-group library --p2p-discovery https://p2p.ivjn.us`; khi khởi động P2P không thành công, HTTP/gốc vẫn được sử dụng và quá trình phát lại không bị gián đoạn.
- **web**: Mở trang `https` ở chế độ toàn màn hình. Android sử dụng WebView trong Hoạt động toàn màn hình (cho phép lưu trữ JavaScript và DOM, đồng thời điều hướng trong trang chỉ tuân theo https; khi quá trình hiển thị gặp sự cố, màn hình sẽ tắt mà không ảnh hưởng đến Agent). Máy tính để bàn bắt đầu quá trình trình duyệt độc lập ở chế độ kiosk, sử dụng thư mục cấu hình tạm thời và kết thúc nó cùng với trình duyệt khi dừng: Windows tìm kiếm Edge, Chrome và Firefox theo trình tự; macOS tìm kiếm Chrome, Edge, Chrome và Firefox (Safari không có chế độ kiosk); Linux tìm kiếm Chrome, Chrome, Edge và Firefox. Trả về lỗi rõ ràng khi không tìm thấy trình duyệt

Android tự động sử dụng một tên miền khác làm nguồn thay thế cho khối dữ liệu và bảng kê khai `.Aran` cho `https://cdn.ivjn.cn` và `https://cdn.ivjn.us`, giữ lại đường dẫn bảng kê khai và khối dữ liệu vẫn đọc `/meta/<SHA1>` từ mỗi nguồn. Các tên miền khác sẽ không được ánh xạ tự động. Khi xảy ra lỗi tạm thời (bao gồm HTTP 554), thời gian chờ hoặc xác minh khối dữ liệu không thành công, nguồn thay thế sẽ được thử trước, sau đó lùi lại 250/500 mili giây trong tối đa 3 vòng; nguồn này sẽ được sử dụng đầu tiên sau khi thành công. Mỗi thời gian chờ kết nối là 5 giây và thời gian chờ đọc là 30 giây; các lỗi cố định như 403/404 không yêu cầu lặp lại cùng một nguồn trong cùng một lần đọc. Kích thước khối và SHA-1 vẫn được xác minh, các chuyển hướng không được tuân theo và xác minh HTTPS không được nới lỏng. Hành vi này yêu cầu Metafile `ab2b92f` hoặc phiên bản mới hơn của SDK Android chứa `MirroredHttpSource`.

Các kênh hình ảnh chỉ hiển thị một mục mỗi lần; hình ảnh mới thay thế mục trước đó. Giữ màn hình luôn bật trong khi thuyết trình (Android: `FLAG_KEEP_SCREEN_ON` + `turnScreenOn` / `showWhenLocked`; Windows: `SetThreadExecutionState`; macOS: `caffeinate -d`; Linux: `systemd-inhibit` hoặc `xdg-screensaver inhibit`). Hình ảnh và văn bản tĩnh trôi pixel chậm để tránh bị cháy màn hình; chỉ lặp lại video; các trang web không bị trôi. Không có viền nổi bật tĩnh. Android thông qua hoạt động toàn màn hình độc lập (không cần khả năng truy cập). Màn hình Linux yêu cầu phiên X11 đã mở khóa và video yêu cầu cài đặt `mpv` hoặc `ffplay` trên máy; Video macOS thích mpv, nếu không thì `open`.

#### Kiểm soát: Tiến trình và trang web

`media_control` kiểm soát nội dung đang được phát theo kênh và trở về trạng thái `media` tương tự. `channel` phải là `sound` hoặc `screen`; `op` lấy một trong các bảng:

| `op.type` | Thông số | Áp dụng |
| --- | --- | --- |
| `pause` / `resume` | — | Âm thanh (âm thanh), video (màn hình) |
| `seek` | `position_ms` (vị trí tuyệt đối, tối đa 7 ngày) | Âm thanh, video |
| `navigate` | `url` (https) | trang web |
| `reload` / `back` / `forward` | — | trang web |
| `scroll` | `dx`, `dy` (pixel, khác 0, mỗi pixel không vượt quá 100000) | trang web |
| `eval` | `script` (tối đa 16 KiB) | Trang web, trả về `script_result` (mã hóa JSON của kết quả, tối đa 64 KiB) |

```sh
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"
```

Đã thêm vào trạng thái: `position_ms`, `duration_ms`, `paused` của `sound` / `screen` (âm thanh và video, khi người chơi có thể báo cáo), `url` của `screen` trên trang web có `title`. Không thể kiểm soát được việc đọc, hình ảnh và văn bản; một lỗi rõ ràng sẽ được trả về khi nội dung trong kênh không khớp với thao tác. Quá trình kiểm tra chỉ ghi lại kênh, loại thao tác và số ký tự tập lệnh chứ không ghi lại URL hoặc nội dung tập lệnh.

- Android: Âm thanh và video được điều khiển trực tiếp bởi `MediaPlayer` / `VideoView` và trang web kiểm soát WebView trong Hoạt động toàn màn hình.
- Windows: Âm thanh được thực thi bởi nhân viên `MediaPlayer` tích hợp; video được điều khiển bởi ống IPC trong mpv, trình phát dự phòng WPF cũng có thể được điều khiển, ffplay không hỗ trợ nó.
- Linux / macOS: Chỉ có thể điều khiển âm thanh và video khi trình phát là mpv (JSON IPC), nếu không nó sẽ trả về "Cài đặt mpv để tạm dừng hoặc tìm kiếm phương tiện trên máy tính để bàn"; khi macOS được trang bị mpv, thay vào đó, âm thanh cũng sẽ được phát bằng mpv.
- Trang web trên máy tính để bàn: Kiosk của Chrome/Edge/Chromium được khởi động bằng `--remote-debugging-port=0` (chỉ liên kết với 127.0.0.1, cổng ngẫu nhiên, thư mục cấu hình tạm thời), được thực thi thông qua giao thức DevTools; Firefox chỉ hỗ trợ `navigate` (khởi động lại kiosk bằng địa chỉ mới).

Điện thoại di động Remote Console cung cấp các tab "Cài đặt hệ thống" và "Phương tiện" trên trang **System Capabilities** của thông tin chi tiết về thiết bị; trang phương tiện có thể đọc trạng thái của hai kênh, dừng âm thanh hoặc hình ảnh tương ứng, phát các bài đọc, URL âm thanh, văn bản, hình ảnh, video và trang web, đồng thời kiểm soát tiến trình của âm thanh, video và các trang web (nhảy, tiến và lùi, làm mới, cuộn, thực thi JS). Khi trang vào nền, kết quả cài đặt, giá trị cài đặt sẽ được gửi, văn bản được đọc to và màn hình hiển thị đầu vào sẽ bị xóa. Bảng điều khiển web cung cấp biểu mẫu cài đặt tương tự và cài đặt trình duyệt sẽ sử dụng lại phiên được mã hóa đã thiết lập; không có trang phát lại phương tiện riêng biệt trên trang web.

### Tìm kiếm văn bản và XML Android

```sh
remote-cli ui realmev5 xml
remote-cli ui realmev5 find-text "设置"
remote-cli ui realmev5 find-text "Settings" --exact --ignore-case
```

Tìm kiếm văn bản khớp với cả văn bản và mô tả của các nút hiển thị. Nó sử dụng đối sánh chuỗi con theo mặc định, với đối sánh chính xác tùy chọn và đối sánh không phân biệt chữ hoa chữ thường. Trả về từng `bounds` và `center` phù hợp (pixel màn hình gốc, có thể nhấp bằng `tap X Y`), ID tài nguyên, văn bản và trạng thái đã bật. Điểm trung tâm được tính từ giao điểm với màn hình để tránh quay về tọa độ nhấp chuột ngoài màn hình; kết quả là duyệt qua tối đa 200 nút và báo cáo bị cắt bớt. Các nút mật khẩu và cây con cấu hình riêng của Agent không tham gia tìm kiếm. Các tính năng trợ năng của Android có thể không đọc được văn bản tự vẽ và giao diện này không thực hiện OCR. Bạn nên tìm kiếm lại sau khi giao diện thay đổi.

Ảnh chụp màn hình là JPEG, có độ dài tối đa 1600 pixel ở cạnh dài nhất và CLI từ chối ghi đè các tệp hiện có. CLI hiển thị hình ảnh đồng thời với kích thước màn hình gốc; nhấp chuột, vuốt và nút cây `bounds` sử dụng pixel màn hình gốc. Khi định vị dựa trên hình thu nhỏ, hãy sử dụng `屏幕坐标 = 图像坐标 × screen_width / width` (sử dụng thang đo chiều cao theo chiều dọc). Đọc lại kích thước sau khi xoay. Cây giao diện có tối đa 200 nút và mỗi chuỗi có tối đa 256 đơn vị UTF-16. `truncated` chỉ ra rằng số lượng nút bị hạn chế. Giao diện tự vẽ của ứng dụng có thể không có các nút truy cập được.

Văn bản và mô tả của nút mật khẩu và cây con cấu hình riêng của Remote Agent sẽ không được trả về; kiểu nhập văn bản từ xa không thể sửa đổi cấu hình của chính nó. Windows được bảo vệ bởi Android `FLAG_SECURE` bị bôi đen hoặc trả về lỗi hệ thống trong ảnh chụp màn hình; Tính năng chia sẻ màn hình của Android 15 cũng có thể tự động chặn các ứng dụng chứa nội dung nhạy cảm như trường mật khẩu. Điều khiển màn hình sử dụng lại xác thực vận hành và bảo trì hiện có và tài khoản `readonly` không thể bắt đầu các hoạt động này. Việc kiểm tra chỉ ghi lại các hành động, tọa độ và số lượng ký tự đầu vào và không lưu văn bản đầu vào, cây giao diện hoặc ảnh chụp màn hình.

**Dừng chia sẻ màn hình** Kết thúc phiên chia sẻ MediaProjection; nếu khả năng truy cập Android 11+ vẫn được bật thì ảnh chụp màn hình trợ năng sẽ vẫn có sẵn. Đóng **Cho phép điều khiển màn hình từ xa** sẽ ngăn tất cả các thao tác màn hình tiếp theo và kết thúc chia sẻ; **Dừng truy cập từ xa** cũng sẽ ngắt kết nối agent. Không thể hoàn tác các lần nhấp hoặc thao tác nhập đã được thực hiện. Quyền truy cập được quản lý riêng biệt bằng cài đặt Android; Ủy quyền hệ thống được giữ lại khi tắt công tắc hoặc dừng dịch vụ, nhưng các thao tác trên màn hình từ xa không được chấp nhận.

Cầu nối cục bộ sử dụng ổ cắm Unix được đặt tên ngẫu nhiên và xác minh rằng người gọi có cùng UID với APK. Điều này ngăn cản sự truy cập của các ứng dụng thông thường khác; shell từ xa được ủy quyền chia sẻ UID ứng dụng với APK và chuyển đổi màn hình không được coi là cách ly quyền đối với shell đó hoặc quản trị viên thiết bị.

## Thông tin và chỉ báo hệ thống

`DeviceInfo.platform` là `linux`, `freebsd`, `netbsd`, `windows`, `android`, `macos` hoặc `openwrt`, `os_version` là các phiên bản hệ thống chung, `capabilities` mô tả `exec`, `pty`, `ubus`, `android_control`. OpenWrt báo cáo `openwrt_version`.

Linux/OpenWrt thu thập số liệu từ `/proc`; Android giữ lại các số liệu có thể đọc được khác khi gặp phải các hạn chế của SELinux và thời gian chạy có thể đạt được thông qua `CLOCK_BOOTTIME`. Windows thu thập bộ nhớ, CPU, thời gian đã trôi qua và số lượng card mạng thông qua API hệ thống gốc. Các chỉ báo không thể thu thập được đánh dấu trong danh sách `unavailable`, CLI và thiết bị đầu cuối di động hiển thị các mục bị thiếu. Windows không có mức tải trung bình của Unix, vì vậy `load` luôn được đánh dấu là không khả dụng.

## Xác minh

```sh
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` là rơle thử nghiệm cục bộ, sử dụng Python `websockets`, để xác minh đăng ký, chỉ báo, thực thi, hết thời gian, hủy, PTY, thay đổi kích thước và ubus không được hỗ trợ của agent thực. Nó không thay thế xác thực và kiểm tra cơ sở dữ liệu Exchange. Sử dụng thông tin xác thực kiểm tra cố định theo hướng dẫn của tập lệnh và không kết nối với các thiết bị hoặc dịch vụ chính thức. Cả Windows và Linux đều phải chạy thử nghiệm gốc của riêng mình; Android cũng phải xác minh nền trước, nền sau, dừng và kết nối lại trong môi trường chạy thực tế sau khi cài đặt APK.

`scripts/agent-ui-smoke.py --agent <binary> --platform linux|windows` xác minh riêng cấu hình đầu tiên của giao diện người dùng, hạn chế xác thực và nguồn, lưu trữ tệp riêng tư, lưu giữ mã thông báo, đăng ký và thực thi thực tế, dọn dẹp khi dừng, kết nối lại sau khi ngắt kết nối và mở lại. Yêu cầu Python 3.11+ và `websockets`; agent cần thử nghiệm trước tiên phải được xây dựng bằng `desktop-ui`. Xác thực trình duyệt cũng phải bao gồm các nút lưu, lỗi đầu vào, khả năng hiển thị mã thông báo, khôi phục làm mới và bố cục màn hình hẹp.

`scripts/android-control-smoke.py` Xác minh hoạt động của màn hình bằng các ứng dụng thử nghiệm Exchange, CLI và Android độc lập thực. Chỉ chạy trong trình mô phỏng chuyên dụng, dùng một lần: Tập lệnh xóa dữ liệu ứng dụng Remote Agent và sửa đổi cài đặt trợ năng. Trước tiên hãy xây dựng và cài đặt hai APK `:app:assembleDebug` và `:control-fixture:assembleDebug`, chuẩn bị Exchange/cơ sở dữ liệu độc lập, đăng ký thiết bị thử nghiệm và lưu mã thông báo thiết bị cũng như cấu hình CLI; Cần có Python 3.11+, Pillow và `adb`.

```sh
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 thay đổi đường dẫn CLI thành `target/debug/remote-cli.exe`. Sản phẩm thử nghiệm được đặt tại `target/android-control-qa/screenshots/` theo mặc định. Bộ cố định chỉ dùng để thử nghiệm và không nhập APK của thiết bị.


### Nhật ký Android, hiển thị trạng thái và đánh thức

Remote Console thêm **DNhật ký thiết bị** và **Màn hình thiết bị** vào thông tin chi tiết về thiết bị. Trang màn hình hiển thị sự hiện diện của màn hình, trạng thái nguồn, trạng thái khóa, kích thước và quyền, đồng thời cung cấp bản xem trước JPEG theo yêu cầu và màn hình **Wake**. Các thiết bị không có màn hình hoặc không có giao diện điều khiển Android đã tắt các thao tác trên màn hình; quyền chụp bị thiếu được hiển thị riêng. Các thiết bị tắt màn hình có thể được đánh thức khi bật điều khiển màn hình cục bộ. Wake không bao giờ loại bỏ keyguard.

Android Agent bao gồm trạng thái **Screen và nhật ký thiết bị** cục bộ. Khi quyền truy cập từ xa được bật, các trường kết nối, đăng ký QR và nút mã kết nối sẽ bị ẩn. Ngắt kết nối yêu cầu xác nhận và bảo tồn thông tin xác thực đã lưu. Ủy quyền truy cập và dịch vụ liên kết tích cực được báo cáo riêng biệt; nếu được ủy quyền nhưng đang chờ liên kết, chỉ chuyển đổi Remote Agent trong cài đặt Trợ năng của hệ thống.

```powershell
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
```

Giao diện `open_android` WebSocket hiện có chấp nhận `{action:"logs",limit:100}` (1-300 mục) và `{action:"wake"}`. Trạng thái thêm tùy chọn `display_present`, `screen_on`, `locked`, `screen_width`, `screen_height` và `accessibility_authorized`; `accessibility_enabled` tiếp tục có nghĩa là dịch vụ đã được kết nối. API màn hình gốc Android vẫn yêu cầu APK và các đại lý không phải Android không quảng cáo giao diện này.

Nhật ký hiện đến từ `logcat -b all`, không có bộ lọc PID, gói hoặc thẻ: hệ thống, ứng dụng, sự kiện và bộ đệm sự cố có sẵn cho nhật ký Android. Mỗi ảnh chụp nhanh trả về tối đa 100 mục trong giao diện người dùng hoặc 1-300 với CLI, giữ lại dấu thời gian, cấp độ, tên bộ đệm, PID/TID, thẻ và thông báo gốc. Đây là lịch sử logcat hiện tại của thiết bị, không phải là kho lưu trữ các tệp nhật ký tùy ý hoặc nhật ký Android đã được luân chuyển.

Cấp quyền ghi nhật ký hệ thống APK cho thiết bị thông qua kết nối ADB được ủy quyền:

```sh
adb shell pm grant com.remote.agent android.permission.READ_LOGS
```

Trên các phiên bản Android yêu cầu quyền truy cập nhật ký mỗi lần sử dụng, hãy mở trang chẩn đoán Agent và chấp nhận xác nhận hệ thống, sau đó thử lại yêu cầu từ xa. Quyền truy cập nền có thể bị từ chối hoặc phê duyệt có thể hết hạn theo chính sách Android/OEM. Android chỉ có thể âm thầm trả về UID của người gọi sau khi bị từ chối, do đó, trình thu thập sẽ kiểm tra siêu dữ liệu UID từ 301 mục logcat mới nhất trước khi chọn đuôi được yêu cầu. Nếu không có UID nào khác xuất hiện (bao gồm cả nhật ký trống), nó sẽ báo cáo rằng không thể xác nhận quyền truy cập toàn bộ thiết bị thay vì hiển thị ảnh chụp nhanh chỉ dành cho ứng dụng. Thiếu quyền hoặc lỗi logcat cũng trả về một lỗi rõ ràng; bộ sưu tập không quay trở lại vòng sự kiện Agent. Bộ sưu tập chạy khỏi chuỗi giao diện người dùng, dừng khi hủy và có thời hạn sáu giây cũng như đầu ra bị giới hạn; ảnh chụp nhanh quá khổ báo cáo bị cắt bớt hoặc có lỗi. Nhật ký sử dụng các phiên điều hành đã được xác thực, định tuyến đối tượng thuê và các hạn chế về vai trò. Văn bản nhật ký thiết bị có thể chứa nội dung được viết bởi các ứng dụng khác. Chế độ xem nhật ký trên web và thiết bị di động sẽ rõ ràng khi thoát trang hoặc ở chế độ nền và không lưu ảnh chụp nhanh vào đĩa. Remote Console cũng xóa ảnh chụp màn hình khi thoát/nền.


**Chia sẻ toàn màn hình trong suốt:** Trong Android Agent chọn **Chia sẻ toàn màn hình với lớp phủ trong suốt**. Cấp quyền hiển thị trên các ứng dụng khác và đồng ý chia sẻ màn hình cho Android. Sau đó, Agent di chuyển xuống nền và cài đặt `TYPE_APPLICATION_OVERLAY` toàn màn hình với độ mờ bằng 0 và `FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE`: tất cả các thao tác chạm sẽ chuyển đến ứng dụng cơ bản. MediaProjection chụp toàn bộ màn hình (Android 14+ yêu cầu hiển thị mặc định một cách rõ ràng). Dừng chia sẻ từ trang hoặc thông báo Agent; dừng chia sẻ không ngắt kết nối Agent. Lớp phủ bị xóa khi quá trình chia sẻ kết thúc hoặc dịch vụ dừng.

Xem trước màn hình và các trang trạng thái Agent được kết nối cho phép chụp. Thiết lập kết nối, vé đăng nhập và trang QR mã thông báo vẫn được bảo vệ. Android vẫn có thể trả về hình ảnh màu đen cho cửa sổ bảo mật của ứng dụng khác; lớp phủ trong suốt không thể vượt qua sự bảo vệ của ứng dụng đó.

Trước khi chụp Android Agent sẽ kiểm tra cả trạng thái hiển thị và trạng thái tương tác PowerManager. Nó chỉ đánh thức màn hình tắt, xác minh rằng nó đã tương tác và từ chối chụp trong khi phím bảo vệ vẫn bị khóa. Trình đọc MediaProjection liên tục thoát khung và giữ lại một hình ảnh được tạo hiện tại thay vì tạo lại bề mặt của nó cho mọi ảnh chụp màn hình. Các khung hình hoàn toàn màu đen chờ khung hình có thể sử dụng được và tạo ra lỗi có thể khắc phục khi hết thời gian thay vì JPEG màu đen thành công.

## So sánh chế độ Agent

Chế độ khởi chạy và nền tảng thiết bị là các kích thước riêng biệt. Các dịch vụ hệ thống và giao diện người dùng cục bộ, không có giao diện nguồn gốc chia sẻ lõi kết nối; `desktop-ui` chỉ thêm cấu hình cục bộ và không cấp quyền từ xa.

| Chế độ/nền tảng | Khả năng | Điều kiện và ranh giới |
|---|---|---|
| giao diện người dùng không có giao diện/cục bộ | Giao thức gốc, lệnh, thiết bị đầu cuối, tệp, số liệu, thông báo, mạng LAN tùy chọn giống nhau | Giao diện người dùng chỉ trên Linux/Windows/macOS; khay Windows; các hành động trên máy tính để bàn yêu cầu phiên người dùng đồ họa có thể sử dụng được |
| Dịch vụ hệ thống/khởi động đăng nhập | Cùng bản địa Agent | Kế thừa tài khoản dịch vụ và môi trường; ghi đè khởi chạy không được sao chép sang dịch vụ khác |
| OpenWrt | Lõi bản địa, ubus | Quảng cáo ubus khi có sẵn; được giám sát bởi proc; không có khả năng điều khiển máy tính để bàn |
| Linux / Windows / macOS | Lõi gốc, điều khiển máy tính để bàn, nhật ký hệ thống, phương tiện | Yêu cầu quyền/công cụ của hệ điều hành; cài đặt hệ thống chỉ được quảng cáo trên Linux/macOS |
| FreeBSD / NetBSD | Lõi gốc, thiết bị đầu cuối, tệp, mạng, số liệu, mạng LAN tùy chọn | Không có pin; Yêu cầu phụ thuộc X11; không có cài đặt phương tiện/hệ thống |
| APK Android | Lõi Rust + Cầu Java, màn hình, cài đặt, phương tiện, OTA | Quyền của UID ứng dụng và hệ điều hành; nhị phân Rust độc lập không có cầu điều khiển APK |
| Tiện ích mở rộng của crom | URL, cookie, trang, cài đặt trình duyệt, CDP, số liệu | Không có lệnh hệ điều hành/PTY/tệp máy chủ tùy ý/trình nghe LAN; CDP đầy đủ cần có sự cho phép của địa phương |

Độ phân giải cấu hình thời gian chạy chia sẻ giao diện người dùng và không giao diện người dùng: CLI/môi trường ghi đè JSON cho mạng LAN, cơ quan giám sát, TLS và thông báo; tự động cập nhật vẫn giữ CLI > JSON > `REMOTE_AUTO_UPDATE` > được bật theo mặc định. `REMOTE_WATCHDOG=1` và 0, đúng/sai, có/không, bật/tắt đều được chấp nhận. Giao diện người dùng vẫn từ chối ghi đè khởi chạy đối với thông tin xác thực kết nối và đường dẫn trạng thái; phần ghi đè thời gian chạy không được lưu vào JSON và các quy trình tự khởi động cần có cấu hình riêng. Trạng thái mạng LAN hiển thị các giá trị hiệu quả; việc lưu trang không xóa phần ghi đè khởi chạy.

Giao diện người dùng và không có giao diện sử dụng JSON `state_file`, nếu không thì mặc định hệ thống liên tục tương tự; các phiên bản thiết bị riêng biệt cần các đường dẫn riêng. Kết nối sẽ làm mới trạng thái và dừng hiển thị đã dừng. Việc dừng sẽ hủy tương lai tự động cập nhật của kết nối đó và giải phóng trạng thái cơ quan giám sát; công nhân thoát ra trong lần kiểm tra tiếp theo và không thể hoàn tác các thao tác đã hoàn thành. Mỗi lần kết nối lại cũng tải lại chính sách nhận dạng và tin cậy của thiết bị; tải danh tính không thành công sẽ xóa danh tính trước đó thay vì giữ lại ủy quyền thiết bị cũ.

Tất cả các hoạt động của thiết bị đều yêu cầu phiên kín và ủy quyền gốc không gian làm việc. Sự đồng ý/quyền không giám sát của CDP trên toàn bộ trình duyệt không bị tách biệt bởi phạm vi cookie đã nhập và không thể bỏ qua các chính sách của Chrome. Việc đóng hoặc ngắt kết nối tiện ích mở rộng yêu cầu một cái bắt tay mới. Bản đồ lưu trữ bản cập nhật máy tính để bàn tới Windows x64, Linux glibc x64/arm64 và macOS arm64; các nền tảng gốc khác sử dụng trình cài đặt/trình quản lý gói, Android sử dụng APK OTA và trình duyệt sử dụng các gói tiện ích mở rộng. Quảng cáo một khả năng không đảm bảo phần cứng, quyền hoặc kho lưu trữ được xuất bản.

## Trình bảo vệ màn hình tự động

Agent cục bộ và cài đặt tiện ích mở rộng trình duyệt hỗ trợ không tải (300 giây theo mặc định), điều khiển bắt đầu/kết thúc và nhập ngay lập tức. Chọn văn bản, hình ảnh/video HTTPS hoặc HTML tĩnh riêng biệt. Vòng lặp video bị tắt tiếng; văn bản và hình ảnh có thể di chuyển trên một phạm vi rộng. Máy tính để bàn và Android cung cấp mật khẩu thoát; cửa sổ toàn màn hình của trình duyệt không có mật khẩu và thoát bằng phím, di chuyển con trỏ hoặc đóng cửa sổ. Chỉ có màn hình đang hoạt động bị che; đây không phải là khóa hệ điều hành. Linux yêu cầu X11 và xprintidle. OpenWrt không có giao diện, Wayland và BSD hiện không hỗ trợ hiển thị màn hình.

Chế độ màu đơn sắc chỉ hiển thị nền đã chọn, không có văn bản hoặc phương tiện. Màu nền sử dụng #RRGGBB; độ sáng dao động từ 0 đến 100% (màu đen ở mức 0%). Android cũng làm mờ đèn nền của cửa sổ bảo vệ màn hình và khôi phục độ sáng của hệ thống khi đầu vào. Các nền tảng khác chỉ làm mờ hình ảnh; Việc tiết kiệm năng lượng của LCD phụ thuộc vào việc điều khiển đèn nền. Lời nhắc thoát vẫn có thể đọc được.

Từ 0.1.34, chuyển động chỉ di chuyển văn bản và hình ảnh trên một khu vực rộng trên nền. Nội dung lớn sẽ giảm tỷ lệ khi cần xuống tối đa 44% chiều rộng và chiều cao có sẵn để các vị trí góc đối diện không chồng lên nhau. HTML, video và màu sắc vẫn giữ nguyên; sở thích giảm chuyển động tạm dừng hành trình. Mọi chế độ đều ẩn con trỏ khỏi khung đầu tiên, khôi phục nó cho hộp thoại mật khẩu thoát và ẩn lại khi hủy. Windows làm mới con trỏ bên trong cửa sổ bảo vệ màn hình cứ sau 100 mili giây mà không thay đổi con trỏ hệ thống. Những thay đổi này xuất hiện đầu tiên trong các gói Windows và Trình duyệt Agent 0.1.34; kiểm tra bảng kê khai tải xuống cho các nền tảng khác.
